Bashでコマンドをバックグラウンド起動する基本形は command & です。直後の $! を変数へ保存すると、そのシェルが開始した非同期コマンドのPIDを追跡できます。終了を待ってステータスを回収するには wait "$pid" を使います。端末入力を必要としない処理では標準入力を /dev/null へ、標準出力と標準エラーを用途別ファイルへリダイレクトします。nohup はSIGHUPを無視させる補助ですが、再起動後の再実行、監視、依存関係、確実な永続化まで提供するサービス管理機能ではありません。長期運用はsystemdユーザーサービスやスケジューラへ移します。 確認ポイント:非同期処理はPID、waitの終了status、stdout・stderrの保存先を一組で管理します。
Bash非同期jobのPIDを直後に保存する
sleep 5 &
job_pid=$!
printf "started pid=%s\n" "$job_pid"
ps -p "$job_pid" -o pid=,stat=,args=
期待出力は、Bashのジョブ通知に続いて started pid=12345 のようなPID、psの 12345 S sleep 5 のような行です。実際の番号は毎回変わります。$! は直近にバックグラウンドへ送ったパイプラインのPIDです。別の ... & を実行する前に保存しないと上書きされます。
プロセス一覧から名前で検索してPIDを推測するより、起動直後の $! を保存する方が対象を限定できます。ただしコマンドが自らforkして親がすぐ終了するデーモン化を行う場合、保存したPIDが実作業プロセスではないことがあります。フォアグラウンド動作を選べるアプリケーションなら、そのモードをサービスマネージャーに監視させます。
waitで完了と終了ステータスを回収する
( sleep 1; exit 7 ) &
job_pid=$!
wait "$job_pid"
job_rc=$?
printf "pid=%s exit=%s\n" "$job_pid" "$job_rc"
約1秒後、期待出力は pid=実際の番号 exit=7 です。wait は指定ジョブが終了するまで現在のシェルを待たせ、その終了ステータスを返します。wait の直後に job_rc=$? と保存するのが重要です。先にechoやpsを実行すると $? はその別コマンドの値になります。
PIDを指定しない単なる wait は、現在のシェルが把握する未完了のバックグラウンドジョブ全部を待ちます。複数処理を個別評価するなら、それぞれの $! を配列などへ保存し、一件ずつ wait してジョブ名、PID、終了値を対応付けます。バックグラウンドにしただけで失敗が成功へ変わることはありません。
標準入力・標準出力・標準エラーを分ける
long_command </dev/null >./worker.out 2>./worker.err &
job_pid=$!
printf "%s\n" "$job_pid" >./worker.pid
この long_command は実際の非対話コマンドへ置き換えます。</dev/null は端末から入力を待たないようにし、>worker.out は標準出力、2>worker.err は標準エラーを別ファイルへ送ります。最後のPIDファイルは新規作成または上書きなので、既存サービスのPIDファイル名を流用せず、ジョブ専用ディレクトリへ排他的に作ります。
標準出力と標準エラーを同じファイルへまとめるなら、順序を含めて >worker.log 2>&1 とします。2>&1 >worker.log では、標準エラーがリダイレクト前の標準出力を指すため同じ結果になりません。ログファイルを検索対象や入力ディレクトリ内に置くと、自分の出力を再処理する可能性があるため分離します。
起動直後に kill -0 "$job_pid" が成功すれば、その時点で同じPIDのプロセスが存在し、シグナル送信権限があることを確認できます。ただし処理内容の正常性は証明しません。数秒後に終了している場合は wait の値と worker.err を確認し、入力待ち、パス誤り、権限不足、設定エラーを区別します。
nohupがすること、しないこと
nohup sh -c 'printf "start\n"; sleep 30; printf "done\n"' </dev/null >./nohup-worker.log 2>&1 &
job_pid=$!
printf 'nohup pid=%s\n' "$job_pid"
GNU nohup は指定コマンドがhangupシグナルを無視するようにして実行します。上の例では入出力をすべて明示したため、既定の nohup.out に依存しません。nohup自体をバックグラウンドへ送る機能は & が担います。シェルや端末エミュレーター、リモートセッション、システムのプロセス管理方針によってログアウト時の扱いは異なり、SIGHUP以外の終了、OS再起動、OOM、アプリケーション障害から守るものではありません。
SSH切断後も実行したいだけの一時作業でも、まず短いsleepでログアウト挙動を検証します。対話入力や端末制御が必要なプログラムを /dev/null 入力で開始すると、即時終了または待ち状態になる場合があります。秘密情報をコマンドライン引数へ書くとプロセス一覧に露出し得るため、認証情報をnohupコマンド文字列へ埋め込みません。
systemdユーザー単位で一時ジョブを管理する
systemd-run --user --unit=report-build --collect /usr/local/bin/report-build
systemctl --user status report-build
journalctl --user-unit=report-build
これはsystemdユーザーマネージャーが利用できる環境で、/usr/local/bin/report-build を一時サービスとして起動する例です。実在する非対話コマンドへ置き換えます。--unit で追跡名を固定し、--collect は終了した一時ユニットの片付けを依頼します。状態と終了結果は systemctl --user status、ログはユーザージャーナルで確認できます。
systemdのユーザーマネージャーがない環境、ユーザーサービスがログアウト時に停止する設定、lingerが許可されていない環境では、ログアウト後の継続性は同じではありません。必要な稼働範囲を管理者と決め、ログアウト後も必要なら正式なユーザーサービス、システムサービス、ジョブスケジューラを選びます。繰り返し実行はsystemd timerやcronなどでスケジュールし、単なるnohupの無期限放置にしません。
正確なPIDだけを安全に終了する
if kill -0 "$job_pid" 2>/dev/null; then
ps -p "$job_pid" -o pid=,lstart=,args=
kill -TERM "$job_pid"
wait "$job_pid"
job_rc=$?
printf "terminated pid=%s wait_status=%s\n" "$job_pid" "$job_rc"
fi
まず保存済みPIDが存在するかを確認し、psで開始時刻とコマンドが自分の起動した対象と一致することを読みます。一致した一件へTERMを送り、wait で終了を回収します。シグナル終了のwait値は128より大きくなるのが一般的で、TERMなら143になる環境が多いものの、数値だけで対象確認を省きません。
名前一致の全プロセスをまとめて止めるコマンドは、同名の別ユーザー処理や本番ジョブを巻き込むためこの手順では使いません。TERM後も即座にKILLへ進まず、アプリケーションが終了処理を行う時間を設けます。systemd-runで起動したジョブならPIDへ直接送るより systemctl --user stop report-build を使い、サービス管理状態とログを一貫させます。
失敗条件と片付け
- 起動直後に終了:
waitの値と標準エラーを確認し、入力・パス・権限を修正してから再実行する。 - 端末を閉じると終了:nohupのSIGHUP範囲、シェルのジョブ方針、systemdユーザーマネージャーの存続範囲を確認する。
- ログが空:出力先、バッファリング、実際に標準出力へ書くプログラムかを確認する。
- PIDファイルが古い:PIDだけで停止せず、開始時刻とコマンドを照合し、ジョブ固有の管理情報を更新する。
- 永続運用が必要:再起動、再試行、ログ、依存関係、スケジュールをサービスまたはタイマー定義へ記述する。
一時unitとPID fileを片付ける
sleepだけの例は終了すれば状態を残しません。リダイレクト例は worker.out、worker.err、worker.pid、nohup-worker.log を作成するため、ジョブ停止と内容確認後に、その例で作った正確なファイルだけを片付けます。systemd-run例は一時ユニットを作る変更で、不要になれば対象名を確認して停止し、--collect による終了後の回収を確認します。恒久的なサービスやタイマーを作る場合は、対象ユニットを無効化・停止して定義を取り除く手順を同じ変更票に含めます。
PID・終了値・logをそろえる
&直後の$!を保存した。wait "$pid"の直後に終了ステータスを回収した。- 非対話処理の標準入力、標準出力、標準エラーを明示した。
- nohupを再起動後の永続化や監視機構と誤認していない。
- 停止時は保存PIDまたはサービス単位を照合し、正確な一件へTERMを送った。

コメント