- systemdからスクリプトを実行すると、ログインシェルが読み込むPATH設定が反映されないため、nvmなどでインストールしたコマンドが見つからなくなります。
- 対策として、スクリプト内でコマンドを絶対パスで呼び出し、変数に入れて管理するのが確実です。
- grepやsedなどシステム標準のコマンドは通常のPATHで見つかるため、基本的には絶対パス指定は不要です。
CLAUDE="/home/username/.nvm/versions/node/v24.14.0/bin/claude"
"$CLAUDE" loginCode language: JavaScript (javascript)
1. sytemdだとPATHが違う?
ふだんターミナルで使っているスクリプトを systemctl から呼び出したら、エラーになっていました。
どうも、ログインシェルでないためにPATH が違うため、nvmなどでインストールしたローカルのCLIコマンドが見つからなくなっていたみたいです。
PATH とは、シェルがコマンドを探すディレクトリの一覧です。
ログインシェルでは ~/.profile や ~/.bashrc、/etc/profile などで読み込みますが、systemd のサービスはこれらを読み込みません。
そのため、PATH に含まれないコマンドが見つからなくなるのです1。
たとえば、nvm(バージョン管理ツール)でインストールした claude や codex などは ~/.nvm/versions/node/vX.X.X/bin/ 以下に入ります2。
このディレクトリは systemd の標準 PATH に含まれないため、特に問題が起きやすいです。
1.1. コマンドを変数で管理する
systemdのサービス登録で、ENVIRONMENTにPATHを登録する方法もありますが、確実なのはスクリプト内でコマンドを絶対パスで呼び出すことです。
たとえば、スクリプトの冒頭でコマンドのパスを変数に登録します。
CLAUDE="/home/username/.nvm/versions/node/v24.14.0/bin/claude"Code language: JavaScript (javascript)
usernameやバージョンは、それぞれの環境に合わせて変えてください。
実行時は引用符を付けて$変数名 で呼び出します。
"$CLAUDE" loginCode language: JavaScript (javascript)
これは、パスに空白が含まれる場合でも安全に動作するための作法です3。
今回は、空白文字がないので、どちらでも効果は変わりませんが、わかりづらいエラーを避けるための習慣ですね。
ちょっと面倒にはなりますが、この方法の利点は、
PATHが異なる環境でも確実に動く- コマンドのパスを一箇所で管理できる
- nvm のバージョンを上げたときは変数の値を書き換えるだけで済む4
1.2. grep や sed はどうするか
このような絶対パスでのコマンド呼び出しは、自分のインストールしたコマンドに対して行えば十分です。
たとえば、grep、sed、awk などまで絶対パスで呼び出すのは大変で、そこまでする必要はありません。
通常 /usr/bin/ に入っており、systemd の標準 PATH で見つかるからです5。
あとは、スクリプト冒頭で PATH を明示しておく方法もあります。
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binCode language: JavaScript (javascript)
ただ、~/.nvm/... のようにユーザーディレクトリ以下に入るコマンドはこの方法では解決しないため、やはり変数で管理する必要は残ります。
2. 動作確認の方法(systemd-run)
systemd の実行環境に近い状態でスクリプトをテストするには systemd-run を使います6。
systemd-run --user --pty /path/to/script.sh
また、環境変数の一覧は次のコマンドで確認できます。
systemctl show-environment
systemd-run --user --pty env
2.1. 起動前の存在確認
スクリプトの冒頭でコマンドの存在を確認しておくと、パスが変わった場合にすぐ検出できます。
[ -x "$CLAUDE" ] || { echo "claude not found"; exit 1; }Code language: PHP (php)
-x は、指定したファイルが存在して実行可能かどうかを確認するテストです7。
- systemd のサービスはクリーンな実行環境で起動し、ログインシェルが読む設定ファイルを一切参照しません。環境変数を渡すには unit ファイルの
Environment=またはEnvironmentFile=を使います。 – Setting Environment Variables for systemd Services - nvm は Node.js のバージョンごとに
$NVM_DIR/versions/node/vX.X.X/以下にファイルを格納します。グローバルインストールした npm パッケージのバイナリも同じディレクトリのbin/に置かれます。 – nvm-sh/nvm - シェルはダブルクォートで囲まれた変数を単一のトークンとして扱います。引用符なしでパスに空白が含まれると、シェルのワード分割によってコマンドと引数が分離され、意図しない動作を引き起こします。 – Bash Reference Manual – Quoting
- nvm でバージョンを切り替えると、使用するバイナリのディレクトリパスが変わります。スクリプト上部の変数定義を一行修正するだけで対応できます。現在のバージョンは
nvm currentで確認できます。 – Installing Multiple Versions of Node.js Using nvm - systemd がサービスに渡すデフォルトの
PATHは/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binです。実際の値はsystemd-run envをジャーナルで確認できます。 – Arch Linux Forums – Best way to set the executable path systemd-runはコマンドをトランジェントサービスとして実行するツールです。--ptyは疑似端末(pseudo TTY)を通じて標準入出力をターミナルに接続するオプションで、インタラクティブな出力が必要なスクリプトのテストに適しています。 – Ubuntu Manpage: systemd-run-xフラグは POSIX で定義されており、[ ]とtestコマンドのどちらでも使えます。ファイルが存在し、かつ実行パーミッション(execute bit)が立っているときに真を返します。 – test – Open Group POSIX 仕様