裏と表のジョブを使い分ける

かつて...

  • 今どきのターミナルはタブやウィンドウをいくつでも開けるから、
  • フォアグラウンドでコマンド実行中に何か別のことがしたくなったら、新たにタブを開いて、そこでコマンドを実行すれば十分用が足りる。
  • バックグラウンドでコマンドを実行できることも知っているけど、体系的に理解してないのでうっかりミスを繰り返した。
    • うっかりバックグラウンドで処理していることを忘れていた...。
    • 処理中だと思っていたら停止していた...。
    • 終了したと思っていたら停止していた...。
    • 停止中のプロセスを再開するにはどうする?
    • やっぱり終了したいんだけど、どうすればいいのか?
  • そんなうっかりを繰り返すうちに、見えないバックグラウンドで実行して悩むくらいなら、常に状態が見えるフォアグラウンドにしておいた方が益し。
  • 好きなだけタブを開いてしまえ、という方針になってしまった。


しかし...

  • 何かの補佐で一時的にバックグラウンドにしておきたいことって、よくある。
    • manでマニュアル見ながら、コマンドオプションを指定したいとか、
    • git commitしてviでメッセージ編集中に過去のコミットメッセージの書き方を見たくなったとか、
    • Rubyで開発中はgem serverをずっと起動しておきたい(ログ不要)とか、
  • そんな時でもフォアグラウンド一筋な自分は、じゃんじゃんタブを開いて調子良く処理するのだけど、
  • その後閉じるの忘れて、ターミナルはタブだらけ...。
  • 元々どのタブで作業していたのかも見失ってしまったりする。

という訳で、フォアグラウンドとバックグラウンドを的確に切り替えて使いこなす技を身に付けるのだ。

七つ道具

  • たった7つのコマンドとキー操作を覚えるだけで、フォアグラウンドとバックグランドを自在に行き来できる。
& コマンドに&を付加することで、バックグラウンドで実行する
jobs 稼働中のジョブを表示する
fg [ジョブ指定] ジョブをフォアグラウンドで実行する
bg [ジョブ指定] 一時停止中のジョブをバックグラウンドで実行する
control-Z フォアグラウンドジョブを一時停止する
control-C フォアグラウンドジョブを終了する
kill ジョブ指定orプロセス指定 ジョブを強制終了・一時停止するなど
      • fg bgコマンドは、[ジョブ指定]を省略するとカレントジョブを指定したことになる。
      • killコマンドは、ジョブ指定orプロセス指定を省略できない。必ず指定すること。

ジョブとプロセスの違い

ジョブとプロセスという言葉が入り混じってきたので、その意味を明確にしておく。

  • プロセス=OSが管理する実行中のプログラムの単位
  • ジョブ=シェルが管理する実行中のプログラムの単位
  • プロセスは、OS環境の中で重複しない番号が割り振られる。どのシェルからも参照可能。
  • ジョブは、起動したシェルごとに番号管理される。起動したシェルのみ参照可能。(他のシェルは参照できない)
  • 複数のプロセスが連携して、一つのジョブを構成することもある。
    • 例:history|awk '{ print $2 }'|sort|uniq -c|sort -r|head
    • 上記ワンライナー(よく使うコマンドのベスト10を表示)は、計6つのプロセスが次々と連携して、一つのジョブを処理するのだ。

jobsコマンド

  • 実験用に3つのジョブをバックグラウンドで起動しておく。
$ gem server -l &>/dev/null &
$ git help log &
$ top &
  • jobsコマンドは、稼働中のジョブリストを返す。
$ jobs
[1]   Running                 gem server -l >&/dev/null &
[2]-  Stopped                 git help log
[3]+  Stopped                 top
  • 出力されたリストには以下のような意味がある。
JobID カレント記号 状態 起動コマンド
[1] Running gem server -l >&/dev/null &
[2] - Stopped git help log
[3] + Stopped top


ジョブの指定方法

  • JobID・カレント記号・起動コマンドのどれかを使って、任意のジョブを指定できる。
  • %に続けて入力することで、ジョブ指定になるのだ。
%番号
  • %番号で、JobIDを指定する。
$ fg %1 # gem server -l >&/dev/null &をフォラグラウンドへ
$ fg %2 # git help logをフォラグラウンドへ
$ fg %3 # topをフォラグラウンドへ
%カレントジョブ記号
  • %+は、カレントジョブを意味する。(%%もカレントジョブ)
  • %-は、一つ前のカレントジョブを意味する。
    • カレントジョブ=いちばん直近に操作(起動・停止・fg bgの切替など)されたジョブ
    • 一つ前のカレントジョブ=カレントジョブの前に操作されたジョブ
$ fg %- # git help logをフォラグラウンドへ
$ fg %+ # topをフォラグラウンドへ
$ fg    # topをフォラグラウンドへ(fg・bgコマンドでは、省略するとカレントジョブと見なされる)
%コマンド名
  • %コマンド名では、コマンド名で始まるジョブを指定できる。(前方一致検索)
  • ジョブを区別可能な最小限の入力でOK。
$ fg %gem # gem server -l >&/dev/null &をフォラグラウンドへ
$ fg %git # git help logをフォラグラウンドへ
$ fg %top # topをフォラグラウンドへ
  • %?コマンド名なら、コマンド名を含むジョブを指定できる。(部分一致検索)
$ fg %?server # gem server -l >&/dev/null &をフォラグラウンドへ

対話的なコマンドのバックグラウンド実行はできない

  • topやman・helpのページャーなど、対話的なコマンド実行中は、バックグラウンドに移行すると自動的に一時停止状態になる。
  • bgでバックグラウンド実行のシグナルを送っても、一時停止状態のまま。
  • 対話的なコマンドを実行状態にするには、フォアグラウンドに移行する必要があるのだ。

だから、上記のgit help logやtopコマンドは、バックグラウンドでは常にStopped状態になってしまうのだ!

ジョブ操作の例

バックグラウンドのプロセスを終了する
  • gem serverをバックグラウンドで起動しておく。
$ gem server -l &>/dev/null &
[1] 72740
  • [1]はジョブID、72740はプロセスID。
      • ちなみにバックグラウンドであっても、gem server -l &だけでは、動作中の出力がフォアグラウンドに表示されてしまう。
      • &>/dev/nullを付けることで、動作中の出力はすべて破棄される。
      • 必要ならファイルに出力することもある。例:gem server -l &> gem_server.log
  • gem serverをフォアグラウンドに呼び戻す。
$ fg
  • gem serverを終了する。
[control-C]
viでコミットメッセージ編集中にgit logを確認したい
  • git commitでviが起動してコミットメッセージ編集中、
$ git commit
  • 以前のコミット履歴を確認したくなったので、一時停止して、git logを確認して、
[control-Z]
$ git log --oneline
  • 再びviのコミットメッセージ編集中の状態に戻る。
$ fg

killコマンド

  • killコマンドはジョブやプロセスを強制終了する時によく使うのだけど、
  • より正確には、ジョブやプロセスに「シグナル」を送るコマンドなのだ。
  • 強制終了するシグナルを送信すれば、ジョブは終了する。
  • 強制終了以外にも様々なシグナルがある。
$ kill -l|awk '{print $1 $2"\n"$3 $4"\n"$5 $6"\n"$7 $8}'
 1) SIGHUP
 2) SIGINT	端末からの割込終了(control-C)
 3) SIGQUIT	終了とコアダンプ(control-\)
 4) SIGILL
 5) SIGTRAP
 6) SIGABRT
 7) SIGEMT
 8) SIGFPE
 9) SIGKILL	無視できない強制終了(kill -KILL)
10) SIGBUS
11) SIGSEGV
12) SIGSYS
13) SIGPIPE
14) SIGALRM
15) SIGTERM	強制終了(kill)
16) SIGURG
17) SIGSTOP	無視できない中断(kill -STOP)
18) SIGTSTP	端末からの中断(control-Z)
19) SIGCONT	再開(bg・fg)
20) SIGCHLD
21) SIGTTIN
22) SIGTTOU
23) SIGIO
24) SIGXCPU
25) SIGXFSZ
26) SIGVTALRM
27) SIGPROF
28) SIGWINCH
29) SIGINFO
30) SIGUSR1
31) SIGUSR2
  • 上記のとおり、control-Zやcontrol-C、bg、fgは、ジョブにシグナルを送っている。
      • fgについては、加えてフォアグラウンドジョブにする処理も行っていると思う。
  • 実行中のバックグラウンドジョブを一時停止するには、fgしてから、control-Zするのだけど、
  • kill -TSTP %ジョブ番号を指定すれば、いちいちフォアグラウンドせずに一時停止できるのだ。
$ sleep 100 &
[1] 78927

$ jobs
[1]+  Running                 sleep 100 &

$ kill -TSTP %%
[1]+  Stopped                 sleep 100
  • ジョブ番号は省略できないけど、stopコマンドをエイリアスで設定してみた。
$ alias stop='kill -TSTP'

図解

ここまでのジョブ操作を図にまとめると、以下のようになる。

  • 太字は、コマンドまたは[キー操作]。

|Background|-------------->|Background|

| | | | | |
| | fg | | kill -TSTP | |
|Running |<--------------|Stopped |<--------------|Running |
| | +----|-----+ | |
| | | fg | |
| |<-------------------|---------------------| |
+----|-----+ | +----|-----+
|[control-C] | kill | kill
| | |
+----V--------------------------V--------------------------V-----+
| Terminated |
+----------------------------------------------------------------+
|

    • >
  • [control-Z]・[control-C]はキー操作。
  • キー操作はフォアグラウンドのジョブに対してのみ作用する。
  • 一方、フォアグラウンドで実行中はコマンドを受け付けてくれないので、
  • 一旦[control-Z]で一時停止してから、bgなどのコマンドで操作する。
  • ジョブ実行中に...
    • シェルのコマンド入力ができない状態=フォアグラウンドジョブ
    • シェルのコマンド入力ができる状態=バックグラウンドジョブ
  • よって、フォアグラウンドジョブは[control-Z]・[control-C]などのキー操作のみ受け付ける。コマンドは使えない。
  • 逆に、バックグラウンドジョブは常にコマンド(bg・fg・kill)で操作する。キー操作は使えない。