Linuxでシステム運用をしていると、数時間〜数日間に及ぶバッチ処理を実行することがあります。手動でプログラムを起動する場合にディスプレイの前にずっと付きっきりでは過労死まっしぐらです。またうっかりTerminalを閉じてしまうと大惨事になってしまうため通常はバックグラウンドで実行します。
その際の具体的なコマンドは以下の通り。
$ nohup (コマンド) &
コマンドの最後に&を付けるだけでバックグラウンドで実行してくれます。ただこれだけだとTerminalを閉じてしばらくすると自動的にプログラムも終了してしまいます。そこでコマンドの冒頭に nohup を付けることで強制終了を防ぐことができます。
標準出力に表示されるはずだった内容はカレントディレクトリに nohup.out というファイル名で記録されます。
またコマンドの実行が終了すると以下のような通知が来ます。
$ nohup sleep 10 & [1]+ 終了 sleep 10
基本的な原理
なぜTerminalを閉じると死ぬの?
例えばSSHで接続した先のサーバで以下のコマンドを実行したとき、プロセスは以下のようになります。
$ sleep 10 &
sshdサーバに接続するとその接続を管理するためのプロセスが作成され、そのプロセスがシェル(この場合はBash)を実行するためのプロセスを作成し、更にコマンド実行するためのプロセスを作成するという階層構造(親子関係)になります。
この時にSSHの接続が切れると、コマンドが実行されている親のプロセスもろとも死んでしまうというわけです。具体的には実行中のコマンドに対してTerminalが切断された際に「SIGHUP」と呼ばれる合図が送られ、それを受け取ったコマンドは通常その場で終了します。
この合図のことをシグナルと呼びます。
nohupを通すとなぜ死ないの?
nohupコマンドはSIGHUPを無視するためです。 実際に試してみます。
Termilalを2つ立ち上げそれぞれSSHでサーバへ接続します。片方でnohup sleep 600 &
を実行し、もう片方ではhtopコマンドなどでプロセスの様子を観察します。
nohupで実行していた方のSSH接続を切断すると、以下のようにSSH関係のプロセスは終了しましたがsleep
コマンド自体は引き続き実行が続いていることを確認できました。
リカバリー
人間である以上ついついうっかりミスをしてしまうもの。リカバリは簡単にできるので二次災害を起こさないよう落ち着いて実行します。
登場人物は3人です。状態に応じてこれらを使い分けます。
- Ctrl+z
- 実行中のコマンドを一時停止する
- bg
- 一時停止中のコマンドをバックグラウンドで実行する
- disown
- シェルが終了しても影響を受けない状態にする(ジョブテーブルから削除する)
「nohup」をつけ忘れた場合
冒頭に「nohup」をつけ忘れた場合はdisown
コマンド一発です。
$ sleep 10 & [1] 402 $ disown
「&」をつけ忘れた場合
最後に「&」をつけ忘れた場合は2つのコマンドを打ちます。
Ctrl+z
でコマンドの実行を一時停止するbg
コマンドでバックグラウンドで再開する
$ sleep 600 ^Z [1]+ 停止 sleep 600 $ bg [1]+ sleep 600 &
「nohup」も「&」もつけ忘れた場合
実行中のコマンドをnohup (コマンド) &
と同じ状態にできます。
Ctrl+z
でコマンドの実行を一時停止するbg
コマンドでバックグラウンドで再開するdisown
コマンドでジョブテーブルから削除する
$ sleep 600 ^Z [1]+ 停止 sleep 600 $ bg [1]+ sleep 600 & $ disown
その他
nohup.log以外のファイルに記録する
リダイレクトするだけです。標準出力の場合は以下の通り。
$ nohup ./foo.sh > log.txt &
標準エラー出力も同様です。標準出力と同時に出力する場合は後者になります。
$ nohup ./foo.sh 2> error.txt &
$ nohup ./foo.sh > log.txt 2> error.txt &
参考ページ
- man nohup