Linuxジョブ制御:&、disown、nohup
1. 概要
Linuxコマンドラインを使用する場合、プロセスを開始して、ジョブとしてバックグラウンドで実行したい場合があります。
このように、端末をブロックすることはなく、実行中に他の作業を行うことができます。
これを達成する方法はいくつかあります。 このチュートリアルでは、3つの一般的なアプローチについて説明します。 否認と nohup コマンド、および & オペレーター。
2. 一言で言えばLinuxジョブ制御
Linuxのジョブ制御の基本を理解しましょう。
2.1. 実行時間の長いプロセスの例
まず、プロセスの例として、実行時間の長いシェルスクリプトを作成しましょう。
$ cat long_running.sh
#!/bin/bash
while true; do
sleep 3
date +"The Process [$$] says: The current date and time is %F %T"
done
スクリプトは非常に単純です。 プロセスIDと現在のタイムスタンプを3秒ごとに出力します。 特別な変数$$は、現在のプロセスのプロセスIDを保持します。
スクリプトが開始されると、たとえば Ctrl-C を押して手動で停止するまで、スクリプトは終了しません。
2.2. 一言で言えばジョブ制御
まず、いくつかのジョブ制御コマンドとキーボードショートカットを見てみましょう。
- Ctrl-C :シグナル SIGINT を送信して、フォアグラウンドで実行されているプロセスを強制終了します
- Ctrl-Z :シグナル SIGTSTP を送信して、フォアグラウンドで実行されているプロセスを一時停止します
- jobs :ジョブのリストとそのステータスを表示します
- fg :バックグラウンドジョブをフォアグラウンドに移動します
- bg :中断されたジョブをバックグラウンドジョブとして実行して再開します
上記のコマンドとキーボードショートカットをよりよく理解するために、例を通してそれらがどのように使用されるかを見ていきます。
まず、サンプルプロセスを開始し、出力を / tmp /outというファイルにリダイレクトしましょう。
$ ./long_running.sh > /tmp/out
良い! シェルスクリプトはフォアグラウンドで実行されています。 スクリプトは永続的に実行されるため、端末をブロックします。
それでは、Ctrl-Zを押して一時停止してみましょう。
$ ./long_running.sh > /tmp/out
^Z
[1] + 61299 suspended ./long_running.sh > /tmp/out
$
出力には、 PID 61299 が一時停止されているジョブが表示され、必要に応じて他のコマンドを入力できるように、コマンドプロンプトが再び表示されます。
jobs コマンドを使用して、それがジョブリストにあるかどうかを確認しましょう。
$ jobs
[1] + suspended ./long_running.sh > /tmp/out
ここで、約30分待ってから、bgコマンドを使用して再開します。
$ bg
[1] + 61299 continued ./long_running.sh > /tmp/out
$
すごい! 一時停止ジョブが再開され、バックグラウンドで実行されます。 今すぐジョブリストを確認すると、次のようになります。
$ jobs
[1] + running ./long_running.sh > /tmp/out
fg コマンドを使用して、フォアグラウンドに戻しましょう。
$ fg
[1] + 61299 running ./long_running.sh > /tmp/out
再びフォアグラウンドで実行され、ターミナルのブロックを再開します。 Ctrl-Cを押して終了できます。
ジョブを終了したら、出力ファイル / tmp /outを確認しましょう。
The Process [61299] says: The current date and time is 2020-05-29 15:50:01
The Process [61299] says: The current date and time is 2020-05-29 15:50:04
The Process [61299] says: The current date and time is 2020-05-29 15:50:07
The Process [61299] says: The current date and time is 2020-05-29 15:50:10
The Process [61299] says: The current date and time is 2020-05-29 15:51:00
The Process [61299] says: The current date and time is 2020-05-29 15:51:03
The Process [61299] says: The current date and time is 2020-05-29 15:51:06
...
出力ファイルを注意深く読むと、4行目と5行目の間隔が50秒であることがわかります。 この期間、出力はありません。 これは、Ctrl-Zを押してジョブを一時停止したためです。
3. The & オペレーター
3.1. の紹介 & オペレーター
コマンドがで終了する場合 & 演算子の場合、シェルはサブシェルのバックグラウンドでコマンドを実行します。 シェルはそれが終了するのを待たず、ステータス0で戻ります。
long_running.shスクリプトで試してみましょう。
$ ./long_running.sh > /tmp/out &
[1] 176933
$ jobs
[1] + running ./long_running.sh > /tmp/out
$ fg
[1] + 176933 running ./long_running.sh > /tmp/out
^C
$ cat /tmp/out
The Process [176933] says: The current date and time is 2020-05-29 21:28:59
The Process [176933] says: The current date and time is 2020-05-29 21:29:02
The Process [176933] says: The current date and time is 2020-05-29 21:29:05
The Process [176933] says: The current date and time is 2020-05-29 21:29:08
The Process [176933] says: The current date and time is 2020-05-29 21:29:11
The Process [176933] says: The current date and time is 2020-05-29 21:29:14
The Process [176933] says: The current date and time is 2020-05-29 21:29:17
この例は、 & 演算子の場合、プロセスはバックグラウンドジョブとして実行されます。 フォアグラウンドで他の作業を並行して実行できます。
3.2. 出力
ちょうど今、 long_running.sh スクリプトを開始したときに、stdoutをファイル/tmp /outにリダイレクトしました。
出力をリダイレクトしないとどうなりますか? 試してみよう:
デモでは、ジョブがバックグラウンドで実行されている場合でも、出力が端末に出力されることがわかります。 それの訳は &演算子によって開始されたバックグラウンドプロセスは、シェルからstdoutとstderrを継承します。
3.3. SIGHUP信号
でプロセスを開始できます & バックグラウンドで実行するための演算子。 ターミナルを閉じても実行されますか? 確認してみましょう。
まず、バックグラウンドでスクリプトを開始します。
$ ./long_running.sh > /tmp/out &
[1] 184314
$ jobs
[1] + running ./long_running.sh > /tmp/out
$ ps -ef | grep '[l]ong'
kent 184314 55850 0 22:01 pts/1 00:00:00 /bin/bash ./long_running.sh
出力は、プロセスがPID 184314で実行されていることを示しています。 次に、現在のターミナルを閉じて新しいターミナルを起動し、ジョブがまだ実行されているかどうかを確認します。
$ jobs
$ ps -ef | grep '[l]ong'
$
おっとっと! ジョブリストは空で、プロセスもなくなりました。 なぜそれが起こったのですか? バックグラウンドジョブプロセスのプロセスツリーは、質問に答えるのに役立つ場合があります。
pstree コマンドを使用して、バックグラウンドジョブプロセスのプロセスツリーを確認してみましょう。
$ ./long_running.sh > /tmp/out &
[1] 191536
$ pstree -s 191536
systemd───xfsettingsd───urxvt───bash───long_running.sh───sleep
出力は、バックグラウンドジョブプロセスがシェルのサブプロセス(この場合は bash )であることを明確に示しています。 さらに、この例では urxvt であるターミナルプロセスは、シェルプロセスの親プロセスです。
SIGHUPは、ターミナルのハングアップが発生したことを通知するために使用されることがわかっています。ターミナルプロセスを強制終了するとどうなるかを理解しましょう。
- ターミナルを閉じます。 端末は、SIGHUP信号をそのサブプロセスに送信します。
- シェルプロセスはSIGHUP信号を受信します。 その後、SIGHUPシグナルをサブプロセスに送信します。
- シェルのサブプロセスとして、バックグラウンドジョブプロセスはSIGHUPシグナルを受け取ります。
プロセスがSIGHUPを受信した場合、デフォルトのアクションはすぐに実行を停止することです。 したがって、バックグラウンドで実行されているジョブプロセスは終了します。
接続された端末が閉じられた後もバックグラウンドジョブを存続させたい場合は、ジョブプロセスがSIGHUP信号を受信しないようにする必要があることは想像に難くありません。
4. disownコマンド
4.1. disownコマンドの概要
disown コマンドは、BashやZshなど、多くの最新のシェルに組み込まれているコマンドです。
異なるシェルからのdisownコマンドは、わずかに異なる動作をする可能性があります。 このチュートリアルでは、Bashの組み込みのdisownコマンドについて説明します。
このコマンドを使用するための基本的な構文は次のとおりです。
disown [options] jobID1 jobID2 ... jobIDN
オプションを指定しない場合、 disown コマンドは、アクティブなジョブのテーブルから指定されたジョブを削除します。
jobID は、%文字で始まります。 たとえば、 %x は、ジョブx。を識別します。
このコマンドのデフォルトの使用法の例を見てみましょう。
$ ./long_running.sh > /tmp/out &
[1] 414860
$ jobs -l
[1] + 414860 running ./long_running.sh > /tmp/out
$ disown %1
$ jobs -l
$
disown コマンドを実行すると、ジョブはジョブリストから削除されます。 ただし、ジョブのプロセスはまだ実行中です。
$ ps -ef | grep '[l]ong'
kent 414860 185196 0 21:31 pts/0 00:00:00 /bin/bash ./long_running.sh
disownコマンドには3つのオプションがあります。
- -a : jobID が指定されていない場合、すべてのジョブを削除します
- -r :ステータスがrunningのジョブのみを削除します
- -h :各ジョブはテーブルから削除されません。 代わりに、シェルが SIGHUP を受信した場合に、SIGHUPがジョブに送信されないようにマークされています。
-hオプションは重要なオプションです。 を使用すると、端末を終了した後、またはリモートサーバーへの接続を切断した後も、バックグラウンドジョブを存続させることができます。
このオプションについては、後のセクションで詳しく説明します。
4.2. 出力
で始まった仕事と同じ & 演算子、の入力と出力否認- edジョブは変更されません。 シェルからstdoutとstderrを再利用します。
4.3. SIGHUPおよび-hオプション
私たちは、 否認コマンドには3つのオプションがあります。 -a 、 -r 、 と
disown -h JobID、を実行すると、ジョブはジョブリストから削除されません。 代わりに、それは仕事をマークします。 シェルがSIGHUPシグナルを送信する場合、シグナルはマークされたジョブに送信されません。 したがって、マークされたバックグラウンドジョブは、制御端末が閉じられていても実行を継続します。
例を通してこれを理解しましょう:
$ ./long_running.sh > /tmp/out &
[1] 546754
$ jobs -l
[1]+ 546754 Running ./long_running.sh > /tmp/out &
$ disown -h %1
$ jobs -l
[1]+ 546754 Running ./long_running.sh > /tmp/out &
出力が示すように、 disown -hを実行した後、ジョブ%1はまだジョブテーブルにあります。 ジョブプロセス546754のプロセスツリーを見てみましょう。
$ pstree -s 546754
systemd───xfsettingsd───urxvt───bash───long_running.sh───sleep
$ ps -ef | grep '[l]ong'
kent 546754 1 0 13:53 pts/0 00:00:00 /bin/bash ./long_running.sh
pstree コマンドの出力は、バックグラウンドジョブプロセスがターミナル urxvt のサブプロセスであることを示し、 ps コマンドの出力は、プロセスがターミナルpts/0。
それでは、ターミナルを閉じましょう。
$ exit
次に、別の端末を開き、プロセス546754がまだ有効であるかどうかを確認します。
$ ps -ef | grep '[l]ong'
kent 546754 1 0 13:53 ? 00:00:00 /bin/bash ./long_running.sh
$ pstree -s 546754
systemd───long_running.sh───sleep
ps コマンドは、long_running.shプロセスがまだ実行中であることを示しています。 ただし、出力を注意深く読むと、 TTY列に疑問符「?」が表示されます。 これは、プロセスに端末が接続されていないことを意味します。
そして、 pstree コマンドの出力はそれを検証します— long_running.sh プロセスは、現在systemsプロセスの直接のサブプロセスです。
バックグラウンドジョブは、シェルのstdoutとstderrを再利用することがわかっています。
言及する価値があります
5. nohupコマンド
nohup ユーティリティは、 GNUCoreutilsパッケージのメンバーです。 その名前は「nohup」を表します。つまり、SIGHUP信号からコマンドを保護できます。
5.1. nohupコマンドの概要
前のセクションで、 disown-hがSIGHUPからバックグラウンドジョブを保護できることを学びました。 nohupコマンドは非常によく似ています。 ただし、バックグラウンドジョブに限定されません。 通常のコマンドでnohupユーティリティを使用できます。
$ nohup ./long_running.sh
nohup: ignoring input and appending output to 'nohup.out'
追加しなかったので & コマンドラインの最後にある演算子を使用すると、スクリプトはフォアグラウンドで実行されます。
long_running.shプロセスのプロセス階層を見てみましょう。
$ ps -ef | grep '[l]ong'
kent 566207 565675 0 18:46 pts/0 00:00:00 /bin/bash ./long_running.sh
$ pstree -s 566207
systemd───xfsettingsd───urxvt───bash───long_running.sh───sleep
ps コマンドの出力には、プロセスIDが表示され、プロセスが端末 pts / 0 に接続されていることも示されますが、pstree出力には示されます。プロセスがターミナルプロセスurxvtに接続されていることを確認します。
次に、ターミナルを閉じて、psコマンドとpstreeコマンドを再度実行します。 彼らが報告するものを見てみましょう:
$ ps -ef | grep '[l]ong'
kent 566207 1 0 18:59 ? 00:00:00 /bin/bash ./long_running.sh
$ pstree -s 566207
systemd───long_running.sh───sleep
ターミナルを閉じた後、 ps コマンドは、 long_running.sh プロセスがまだ実行中であるが、ターミナルに接続していないことを示します。 次に、 pstree コマンドの出力は、それがsystemdプロセスの直接のサブプロセスであることを示しています。
上記の例では、nohupにフォアグラウンドでプロセスを開始させます。 必要に応じて、 nohup ユーティリティを使用してプロセスを開始し、バックグラウンドジョブとして実行させることができます。 追加するだけです & 最後の演算子:
$ nohup ./long_running.sh &
[1] 571215
nohup: ignoring input and appending output to 'nohup.out'
$ jobs -l
[1] + 571215 running nohup ./long_running.sh
ターミナルを閉じると、long_running.shプロセスは引き続き実行されます
5.2. 出力
nohup コマンドによって開始されたプロセスは、シェルのstdoutおよびstderrを継承しません。
nohup: ignoring input and appending output to 'nohup.out'
6. ジョブ制御アプローチの比較
これまで、バックグラウンドジョブプロセスを開始するための3つのアプローチと、それらがSIGHUP信号を処理する方法について説明してきました。
私たちが議論した戦略を要約しましょう:
関数 | stdout / s tderr | ターミナルを閉じた後( SIGHUP ) | |
---|---|---|---|
& | バックグラウンドジョブとしてコマンドを実行する | シェルから継承 | ジョブは停止されます |
否認 | –ジョブリストからジョブを削除します
– SIGHUPからジョブを保護します |
–シェルから継承
– -h オプションを使用していて、制御端末が閉じている場合は破棄されます |
-h オプションを使用した場合にのみ、ジョブプロセスは実行を継続します |
nohup | SIGHUPからコマンドを保護します | nohup.outファイルにリダイレクトされました | コマンドプロセスは実行を継続します |
7. 結論
この記事では、Linuxでのジョブ制御の概念を簡単に紹介しました。 後で、私たちは & オペレーターと否認と nohup コマンド。
また、 SIGHUP シグナルからプロセスを保護して、接続されている端末が閉じられたときにプロセスが強制終了されないようにする方法についても説明しました。