Bashのジョブ制御を使用してフォアグラウンドプロセスとバックグラウンドプロセスを管理する方法
序章
前のチュートリアルでは、ps
、kill
、およびnice
コマンドを使用してシステム上のプロセスを制御する方法について説明しました。 このガイドでは、bash
、Linuxシステム、および端末がどのように連携してプロセスとジョブ制御を提供するかについて説明します。
この記事では、フォアグラウンドプロセスとバックグラウンドプロセスの管理に焦点を当て、シェルのジョブ制御機能を活用してコマンドの実行方法をより柔軟にする方法を示します。
前提条件
このガイドに従うには、bash
シェルインターフェイスを実行しているコンピューターにアクセスする必要があります。 bash
は、多くのLinuxベースのオペレーティングシステムのデフォルトシェルであり、macOSを含む多くのUnixライクなオペレーティングシステムで使用できます。 このチュートリアルは、Ubuntu20.04を実行しているLinux仮想プライベートサーバーを使用して検証されていることに注意してください。
このガイドに従うためにリモートサーバーを使用する場合は、最初に初期サーバーセットアップガイドを完了することをお勧めします。 そうすることで、sudo
権限を持つroot 以外のユーザーや、UFWで構成されたファイアウォールなどの安全なサーバー環境がセットアップされます。これを使用してLinuxスキルを構築できます。
または、このページに埋め込まれているインタラクティブ端末を使用して、このチュートリアルのサンプルコマンドを試すこともできます。 次のインタラクティブターミナルの起動!ボタンをクリックしてターミナルウィンドウを開き、Linux(Ubuntu)環境での作業を開始します。
インタラクティブターミナルを起動します!
フォアグラウンドプロセスの管理
Linuxマシンで開始するほとんどのプロセスは、フォアグラウンドで実行されます。 コマンドは実行を開始し、プロセスの間、シェルの使用をブロックします。 このプロセスでは、ユーザーとの対話が可能になる場合もあれば、プロシージャを実行して終了する場合もあります。 デフォルトでは、すべての出力がターミナルウィンドウに表示されます。 次のサブセクションでは、フォアグラウンドプロセスを管理する基本的な方法について説明します。
プロセスの開始
デフォルトでは、プロセスはフォアグラウンドで開始されます。 これは、プログラムが終了するか状態が変わるまで、シェルと対話できないことを意味します。
一部のフォアグラウンドコマンドは非常にすばやく終了し、ほとんどすぐにシェルプロンプトに戻ります。 たとえば、次のコマンドはHello World
を端末に出力してから、コマンドプロンプトに戻ります。
echo "Hello World"
OutputHello World
他のフォアグラウンドコマンドは実行に時間がかかり、その間シェルアクセスをブロックします。 これは、コマンドがより広範な操作を実行しているため、またはコマンドが明示的に停止されるまで、または他のユーザー入力を受け取るまで実行されるように構成されているためである可能性があります。
無期限に実行されるコマンドは、top
ユーティリティです。 開始後、ユーザーがプロセスを終了するまで、実行を継続し、表示を更新します。
top
q
を押すと、top
を終了できますが、他の一部のプロセスには専用の終了機能がありません。 それらを停止するには、別の方法を使用する必要があります。
プロセスの終了
コマンドラインで単純なbash
ループを開始するとします。 例として、次のコマンドは、Hello World
を10秒ごとに出力するループを開始します。 このループは、明示的に終了するまで永久に続きます。
while true; do echo "Hello World"; sleep 10; done
top
とは異なり、このようなループには「終了」キーがありません。 信号を送信してプロセスを停止する必要があります。 Linuxでは、カーネルは、実行中のプロセスにシグナルを送信して、プロセスが終了または状態を変更するように要求できます。 Linux端末は通常、ユーザーがCTRL + C
キーの組み合わせを押すと、「SIGINT」信号(「信号割り込み」の略)を現在のフォアグラウンドプロセスに送信するように構成されています。 SIGINTシグナルは、ユーザーがキーボードを使用して終了を要求したことをプログラムに通知します。
開始したループを停止するには、CTRL
キーを押したまま、C
キーを押します。
CTRL + C
ループが終了し、制御がシェルに戻ります。
CTRL + C
の組み合わせによって送信されるSIGINTシグナルは、プログラムに送信できる多くのシグナルの1つです。 ほとんどの信号にはキーボードの組み合わせが関連付けられていないため、代わりにkill
コマンドを使用して送信する必要があります。これについては、このガイドの後半で説明します。
プロセスの一時停止
前述のように、フォアグラウンドプロセスは、実行中はシェルへのアクセスをブロックします。 フォアグラウンドでプロセスを開始した後、ターミナルにアクセスする必要があることに気付いた場合はどうなりますか?
送信できるもう1つの信号は、「SIGTSTP」信号です。 SIGTSTPは「信号端子停止」の略で、通常は信号番号20で表されます。 CTRL + Z
を押すと、端末は「サスペンド」コマンドを登録し、SIGTSTP信号をフォアグラウンドプロセスに送信します。 基本的に、これによりコマンドの実行が一時停止し、制御が端末に戻ります。
説明のために、ping
を使用して5秒ごとにgoogle.com
に接続します。 次のコマンドは、ping
コマンドの前にcommand
を付けます。これにより、コマンドに最大カウントを人為的に設定するシェルエイリアスをバイパスできます。
command ping -i 5 google.com
CTRL + C
でコマンドを終了する代わりに、CTRL + Z
を押してください。 そうすることで、次のような出力が返されます。
Output[1]+ Stopped ping -i 5 google.com
ping
コマンドが一時的に停止され、シェルプロンプトに再びアクセスできるようになりました。 ps
プロセスツールを使用して、これを表示できます。
ps T
Output PID TTY STAT TIME COMMAND 26904 pts/3 Ss 0:00 /bin/bash 29633 pts/3 T 0:00 ping -i 5 google.com 29643 pts/3 R+ 0:00 ps t
この出力は、ping
プロセスがまだリストされているが、「STAT」列に「T」が含まれていることを示しています。 ps
のマニュアルページによると、これは「[a]ジョブ制御信号によって停止された」ジョブを意味します。
このガイドでは、プロセス状態をより詳細に変更する方法の概要を説明しますが、今のところ、次のように入力することで、フォアグラウンドでコマンドの実行を再開できます。
fg
プロセスが再開したら、CTRL + C
で終了します。
バックグラウンドプロセスの管理
フォアグラウンドでプロセスを実行する代わりの主な方法は、プロセスをバックグラウンドで実行できるようにすることです。 バックグラウンドプロセスは、それを開始した特定の端末に関連付けられていますが、シェルへのアクセスをブロックしません。 代わりに、バックグラウンドで実行され、コマンドの実行中にユーザーがシステムと対話できるようにします。
フォアグラウンドプロセスがそのターミナルと相互作用する方法のため、ターミナルウィンドウごとに1つのフォアグラウンドプロセスしか存在できません。 バックグラウンドプロセスは、プロセスが完了するのを待たずにすぐに制御をシェルに戻すため、多くのバックグラウンドプロセスを同時に実行できます。
プロセスの開始
コマンドの最後にアンパサンド文字(&
)を追加することで、バックグラウンドプロセスを開始できます。 これは、プロセスが完了するのを待たずに、実行を開始し、すぐにユーザーをプロンプトに戻すようにシェルに指示します。 コマンドの出力は(リダイレクトされたを除いて)引き続きターミナルに表示されますが、バックグラウンドプロセスが続行されるときに追加のコマンドを入力できます。
たとえば、次のように入力することで、バックグラウンドで前のセクションから同じping
プロセスを開始できます。
command ping -i 5 google.com &
bash
ジョブ制御システムは、次のような出力を返します。
Output[1] 4287
次に、ping
コマンドから通常の出力を受け取ります。
OutputPING google.com (74.125.226.71) 56(84) bytes of data. 64 bytes from lga15s44-in-f7.1e100.net (74.125.226.71): icmp_seq=1 ttl=55 time=12.3 ms 64 bytes from lga15s44-in-f7.1e100.net (74.125.226.71): icmp_seq=2 ttl=55 time=11.1 ms 64 bytes from lga15s44-in-f7.1e100.net (74.125.226.71): icmp_seq=3 ttl=55 time=9.98 ms
ただし、同時にコマンドを入力することもできます。 バックグラウンドプロセスの出力は、フォアグラウンドプロセスの入力と出力の間で混合されますが、フォアグラウンドプロセスの実行に干渉することはありません。
バックグラウンドプロセスの一覧表示
停止またはバックグラウンドで実行されたすべてのプロセスを一覧表示するには、jobs
コマンドを使用できます。
jobs
以前のping
コマンドをバックグラウンドで実行している場合、jobs
コマンドの出力は次のようになります。
Output[1]+ Running command ping -i 5 google.com &
これは、現在単一のバックグラウンドプロセスが実行されていることを示しています。 [1]
は、コマンドのジョブ仕様またはジョブ番号を表します。 これは、kill
、fg
、bg
などの他のジョブおよびプロセス制御コマンドで、ジョブ番号の前にパーセント記号を付けることで参照できます。 この場合、このジョブを%1
として参照します。
バックグラウンドプロセスの停止
現在のバックグラウンドプロセスをいくつかの方法で停止できます。 最も簡単な方法は、kill
コマンドを関連するジョブ番号とともに使用することです。 たとえば、次のように入力して、実行中のバックグラウンドプロセスを強制終了できます。
kill %1
端末の構成に応じて、すぐに、または次にENTER
を押したときに、ジョブの終了ステータスが出力に表示されます。
Output[1]+ Terminated command ping -i 5 google.com
jobs
コマンドをもう一度チェックすると、現在のジョブはありません。
プロセス状態の変更
バックグラウンドでプロセスを開始および停止する方法がわかったので、プロセスの状態の変更について学習できます。
このガイドでは、プロセスの状態を変更する1つの方法、つまりCTRL + Z
を使用してプロセスを停止または一時停止する方法についてすでに説明しました。 プロセスがこの停止状態にある場合、フォアグラウンドプロセスをバックグラウンドに移動したり、その逆を行ったりすることができます。
フォアグラウンドプロセスをバックグラウンドに移動する
コマンドを開始するときに&
でコマンドを終了するのを忘れた場合でも、プロセスをバックグラウンドに移動できます。
最初のステップは、CTRL + Z
でプロセスを再度停止することです。 プロセスが停止したら、bg
コマンドを使用して、バックグラウンドでプロセスを再開できます。
bg
今度はアンパサンドが追加されたジョブステータス行が再び表示されます。
Output[1]+ ping -i 5 google.com &
デフォルトでは、bg
コマンドは最後に停止されたプロセスで動作します。 複数のプロセスを再開せずに連続して停止した場合は、ジョブ番号で特定のプロセスを参照して、正しいプロセスをバックグラウンドに移動できます。
すべてのコマンドをバックグラウンドで実行できるわけではないことに注意してください。 一部のプロセスは、アクティブな端末に直接接続された標準の入出力で開始されたことを検出すると、自動的に終了します。
バックグラウンドプロセスをフォアグラウンドに移動する
fg
と入力して、バックグラウンドプロセスをフォアグラウンドに移動することもできます。
fg
これは、最近バックグラウンドで実行されたプロセス(jobs
コマンドの出力の+
で示されます)で動作します。 プロセスを即座に一時停止し、フォアグラウンドに配置します。 別のジョブを指定するには、そのジョブ番号を使用します。
fg %2
ジョブがフォアグラウンドになったら、CTRL + C
でジョブを強制終了するか、完了させるか、一時停止して再びバックグラウンドに移動できます。
SIGHUPの取り扱い
プロセスがバックグラウンドにあるかフォアグラウンドにあるかにかかわらず、プロセスはそれを開始したターミナルインスタンスとかなり緊密に結びついています。 端末が閉じると、通常、端末に関連付けられているすべてのプロセス(フォアグラウンド、バックグラウンド、または停止)にSIGHUP信号が送信されます。 これは、プロセスの制御端末がまもなく使用できなくなるため、プロセスが終了するように通知します。
ただし、端末を閉じてもバックグラウンドプロセスを実行したままにしたい場合があります。 これを達成する方法はいくつかあります。 より柔軟な方法の1つは、screenやtmuxなどのターミナルマルチプレクサを使用することです。 別の解決策は、 dtach のように、screen
およびtmux
のデタッチ機能を提供するユーティリティを使用することです。
ただし、これは常にオプションであるとは限りません。 これらのプログラムが利用できない場合や、実行を継続するために必要なプロセスをすでに開始している場合があります。 時には、これらはあなたが達成する必要があることに対してやり過ぎかもしれません。
nohup
を使用する
プロセスの開始時に、プロセスが完了する前にターミナルを閉じる必要があることがわかっている場合は、nohup
コマンドを使用してターミナルを開始できます。 これにより、開始されたプロセスはSIGHUP信号の影響を受けなくなります。 ターミナルが閉じても実行を継続し、initシステムの子として再割り当てされます。
nohup ping -i 5 google.com &
これにより、次のような行が返され、コマンドの出力がnohup.out
というファイルに書き込まれることを示します。
Outputnohup: ignoring input and appending output to ‘nohup.out’
このファイルは、書き込み可能な場合は現在の作業ディレクトリに配置されますが、それ以外の場合はホームディレクトリに配置されます。 これは、ターミナルウィンドウを閉じても出力が失われないようにするためです。
ターミナルウィンドウを閉じて別のウィンドウを開くと、プロセスは引き続き実行されます。 各ターミナルインスタンスは独自の独立したジョブキューを維持しているため、jobs
コマンドの出力には表示されません。 ターミナルを閉じると、ping
プロセスがまだ実行されていても、ping
ジョブが破棄されます。
ping
プロセスを強制終了するには、そのプロセスID(または「PID」)を見つける必要があります。 これは、pgrep
コマンドを使用して実行できます(pkill
コマンドもありますが、この2つの部分からなる方法により、目的のプロセスのみを強制終了できます)。 pgrep
および-a
フラグを使用して、実行可能ファイルを検索します。
pgrep -a ping
Output7360 ping -i 5 google.com
次に、返されたPIDを参照してプロセスを強制終了できます。これは、最初の列の番号です。
kill 7360
nohup.out
ファイルが不要になった場合は、削除することをお勧めします。
disown
を使用する
nohup
コマンドは便利ですが、プロセスの開始時に必要になることがわかっている場合に限ります。 bash
ジョブ制御システムは、組み込みのdisown
コマンドを使用して同様の結果を達成する他の方法を提供します。
disown
コマンドは、デフォルトの構成で、端末のジョブキューからジョブを削除します。 これは、fg
、bg
、CTRL + Z
、CTRL + C
など、このガイドで前述したジョブ制御メカニズムを使用して管理できなくなったことを意味します。 代わりに、ジョブはjobs
出力のリストからすぐに削除され、端末に関連付けられなくなります。
このコマンドは、ジョブ番号を指定して呼び出されます。 たとえば、ジョブ2をすぐに否認するには、次のように入力します。
disown %2
これにより、制御端末が閉じられた後、プロセスはnohup
プロセスと同じ状態になります。 例外は、ファイルにリダイレクトされていない場合、制御端末が閉じるときに出力が失われることです。
通常、ターミナルウィンドウをすぐに閉じない場合は、プロセスをジョブ制御から完全に削除する必要はありません。 代わりに、-h
フラグをdisown
プロセスに渡して、プロセスにSIGHUP信号を無視するようにマークを付けることができますが、それ以外の場合は通常のジョブとして続行します。
disown -h %1
この状態では、通常のジョブ制御メカニズムを使用して、ターミナルを閉じるまでプロセスを制御し続けることができます。 ターミナルを閉じると、起動時にファイルにリダイレクトしなかった場合、出力する場所がないプロセスで再びスタックします。
これを回避するには、プロセスがすでに実行された後で、プロセスの出力をリダイレクトしてみてください。 これはこのガイドの範囲外ですが、この投稿は、その方法について説明しています。
huponexit
シェルオプションの使用
bash
には、子プロセスのSIGHUP問題を回避する別の方法があります。 huponexit
シェルオプションは、bash
が終了時に子プロセスSIGHUP信号を送信するかどうかを制御します。
注:huponexit
オプションは、シェル自体内からシェルセッションの終了が開始された場合のSIGHUPの動作にのみ影響します。 これが当てはまる例としては、セッション内でexit
コマンドまたはCTRL + D
が押された場合があります。
ターミナルプログラム自体を介して(ウィンドウを閉じるなどして)シェルセッションが終了すると、コマンドhuponexit
はnoに影響します。 bash
がSIGHUPシグナルを送信するかどうかを決定する代わりに、端末自体がSIGHUPシグナルをbash
に送信します。これにより、シグナルは子プロセスに正しく伝播されます。
前述の警告にもかかわらず、huponexit
オプションはおそらく管理が最も簡単なオプションの1つです。 次のように入力すると、この機能がオンかオフかを判断できます。
shopt huponexit
オンにするには、次のように入力します。
shopt -s huponexit
ここで、exit
と入力してセッションを終了すると、プロセスはすべて実行され続けます。
exit
これには、最後のオプションと同じプログラム出力に関する注意事項があるため、これが重要な場合は、ターミナルを閉じる前にプロセスの出力をリダイレクトしたことを確認してください。
結論
ジョブ制御と、フォアグラウンドおよびバックグラウンドプロセスの管理方法を学習すると、コマンドラインでプログラムを実行する際の柔軟性が高まります。 多くのターミナルウィンドウやSSHセッションを開く代わりに、プロセスを早期に停止したり、必要に応じてバックグラウンドに移動したりすることで解決できることがよくあります。