ジョブ制御入門 - jobs/fg/bg/Ctrl+Z

ジョブ制御入門 - jobs/fg/bg/Ctrl+Z

この記事でできるようになること

  • Ctrl+Z で実行中のコマンドを 一時停止 して端末を取り戻せる
  • bg / fgバックグラウンド ↔ フォアグラウンド を自在に往復できる
  • jobs で現在の ジョブ一覧 を確認、%n で個別に指定できる
  • & を末尾に付けて 最初からバックグラウンド実行 できる
  • 「端末が固まった」「終了したのにジョブが消えない」等の 典型的な詰まりどころ を抜けられる

対象読者: ターミナルで vim や長時間のコマンドを実行中に「ちょっとだけ別のコマンドを打ちたい」と思って詰まった経験のある方。Ctrl+C で全部止めてしまって悔しい思いをした方。

導入:リナが Ctrl+C で泣いた日

リナ: 先輩、聞いてください! vim で長い設定ファイルを編集してる途中で、別のターミナルで ls したくなったんですけど、新しいターミナルを開く方法が分からなくて、つい Ctrl+C 押したら vim ごと終了しちゃって…未保存の変更が全部消えました…。
ライニー先輩: あー、それは痛いね。でも安心して、それを防ぐためのキー操作があるよ。Ctrl+Z(コントロール ジー)。
リナ: シーじゃなくてジー、ですか?
ライニー先輩: そう。Ctrl+C は「実行中のコマンドを 殺す」けど、Ctrl+Z は「実行中のコマンドを 一時停止して脇に置く」だけ。端末は手元に戻ってくるから別コマンドを打てるし、用が済んだら fg で元の作業に戻れる。
リナ: えっ、そんな魔法みたいなのが…!
ライニー先輩: 「ジョブ制御」って言って、シェル(bash や zsh)が持ってる基本機能の 1 つ。今日はこの 4 つだけ覚えればいい:Ctrl+Z(一時停止)/ bg(裏で再開)/ fg(前に戻す)/ jobs(一覧)

結論(実務の型)

  • 端末を取り戻したいだけ → Ctrl+Zbg(裏で動かしたまま端末解放)
  • 一時停止してそのまま放置 → Ctrl+Z だけ(停止状態)
  • 元の作業に戻る → fg
  • 何が動いてるか分からなくなったら → jobs

前提(対象環境)

  • bash / zsh(ジョブ制御は POSIX シェルの標準機能)
  • dash や非対話シェルでは効かない(/bin/sh -c 等)
  • 本記事のキー操作は端末上の 対話的セッション が前提

1. 「ジョブ」と「プロセス」は何が違う?

リナ: そもそも「ジョブ」って何ですか?「プロセス」とは別もの?
ライニー先輩: 似てるけど視点が違うんだ。プロセス は OS から見た「動いているプログラム 1 つ 1 つ」。一方の ジョブシェルから見た「あなたが打ったコマンド 1 つの単位」
リナ: つまりジョブはシェル目線、プロセスは OS 目線?
ライニー先輩: そう。例えば ls | grep .txt | sort をパイプで打つと、プロセスは 3 つ作られるけど、シェルから見ればこれは ジョブ 1 つ として扱う。Ctrl+Z を押すとシェルはジョブ単位で一時停止してくれる。

ジョブ番号と PID の違い

種類 付与する人 用途
ジョブ番号 %1 シェル fg %1 / kill %1 等で指定
PID 12345 カーネル kill 12345 / ps の出力

覚え方: ジョブ番号には先頭に % を付ける。fg 1 ではなく fg %1kill 1 ではなく kill %1(後者の kill 1 は PID 1 つまり init を殺そうとする危険なコマンドになる)。

2. Ctrl+Z:今すぐ一時停止する

ライニー先輩: 一番大事なキー操作。vim でも tail -f でも python の対話モードでも、何でもいいから 1 つコマンドを起動して、画面が動いてる状態で Ctrl+Z を押してみよう。
リナ: えっ、止まっちゃうのは怖いです…元に戻せますか?
ライニー先輩: 戻せる。「一時停止」は「殺す」じゃない。冷凍庫に入れてる状態。fg で解凍すれば何事もなく動き出すよ。

試してみる

$ sleep 100

sleep 100 は 100 秒間何もせずに待つコマンド。端末がこの 100 秒間ロックされる。

ここで Ctrl+Z を押す。

^Z
[1]+  Stopped                 sleep 100
$

[1]+ Stopped が出れば成功。$ プロンプトが戻ってきていることに注目。端末が手元に戻った

[1]+ の意味

  • [1] = ジョブ番号 1 番
  • + = 直近に操作したジョブ(fg / bg をデフォルトで適用する対象)
  • Stopped = 停止中(CPU を使っていない)

この状態で別コマンドを打てる

$ ls
$ pwd
$ echo "別のことができる"

sleep は冷凍庫の中で停止したまま、自由に他のコマンドを実行できる。これが Ctrl+Z の威力。

3. fg:呼び戻す / bg:裏で再開する

リナ: 停止したジョブはどうやって動かせばいいですか?
ライニー先輩: 2 つの選択肢がある。fg は「前面に呼び戻して再開」、bg は「停止したまま裏で再開」。前者はあなたが画面に向き合って続ける、後者はあなたが別の作業を続ける。

fg:フォアグラウンドへ戻す

さっきの sleep 100 を戻してみる。

$ fg
sleep 100

sleep の続きが始まり、再び端末がロックされる。残り時間を待つか、もう一度 Ctrl+Z で止めるか、Ctrl+C で殺すか。

bg:バックグラウンドで再開

「裏で動かしながら端末は使い続けたい」場合は bg

$ sleep 100
^Z
[1]+  Stopped                 sleep 100
$ bg
[1]+ sleep 100 &
$

Stopped から &(バックグラウンドで実行中)に変わった。これで sleep は裏で時計を進めながら、あなたは他の作業ができる。

bg できないコマンドもある: vim のような対話的なフルスクリーンアプリは「裏でキー入力を待つ」ことができないため、bg すると停止状態に戻ってしまう(Stopped (tty input))。vimCtrl+Z で停止 → 用が済んだら fg の往復が基本パターン。

4. & を末尾に付ける:最初からバックグラウンド

ライニー先輩: 最初から「これは裏で動かす」と決まってるなら、コマンドの末尾に & を付けて起動 するのが手っ取り早い。
リナ: Ctrl+Zbg の 2 ステップを 1 ステップで済ませるってことですか?
ライニー先輩: そう、結果は同じ。Ctrl+Z → bg は「実行してから気が変わって裏に回す」、& は「最初から裏で動かす」っていう違い。

& の使い方

$ sleep 100 &
[1] 12345
$

[1] がジョブ番号、12345 が PID。即座にプロンプトが戻る。

$ jobs
[1]+  Running                 sleep 100 &

Running 状態。停止せずに動いている。

よく使う実例:ログ収集を裏で

$ tail -f /var/log/syslog > mylog.txt &
[1] 23456
$ # この間に別の作業
$ vim config.txt

tail -f を裏で動かしながら vim で設定編集、みたいな並行作業ができる。

& だけだと出力が混ざる: バックグラウンドジョブの標準出力は そのまま端末に流れる(リダイレクトしない場合)。あなたが他のコマンドを打ってる最中に突然出力が割り込んでくるので、長時間動かすなら > file 2>&1 でファイルへ逃がすこと。

5. jobs:今動いてるものを全部見る

リナ: 複数のジョブを動かしてると、どれがどれだか分からなくなりそうです。
ライニー先輩: そんなときは jobs 一発。今のシェルが管理してるジョブを全部一覧で出してくれる。

基本:jobs

$ sleep 100 &
[1] 12345
$ sleep 200 &
[2] 12346
$ vim notes.txt
# Ctrl+Z で停止
$ jobs
[1]   Running                 sleep 100 &
[2]-  Running                 sleep 200 &
[3]+  Stopped                 vim notes.txt

3 つのジョブが見える。

  • + = 直近操作したジョブ(fg / bg のデフォルト対象 = vim
  • - = その 1 つ前のジョブ(= sleep 200

ジョブを個別に指定:%n

$ fg %1     # sleep 100 を前面に
$ bg %3     # vim を裏に(停止状態のまま)
$ kill %2   # sleep 200 を終了

% プレフィックスを忘れない

kill 1PID 1(init)を殺すコマンド。一般ユーザーでは権限不足で弾かれるが、root で実行するとシステム自体が止まりかねない。「ジョブを指定する時は必ず %」を体に染み込ませること。

jobs の便利オプション

オプション 効果
jobs -l PID も併せて表示
jobs -p PID だけを表示(スクリプト用)
jobs -r 実行中ジョブだけ
jobs -s 停止中ジョブだけ

6. ターミナルを閉じたらジョブはどうなる?

リナ: バックグラウンドで動かしたまま、ターミナルを閉じたらどうなるんですか?
ライニー先輩: 普通は 死ぬ。シェルが終わるときに、配下のジョブに SIGHUP(ハングアップ信号) を送るのがデフォルト動作だから。
リナ: えっ、長時間処理を裏で動かして油断してたら全部消えちゃう?
ライニー先輩: そう。それを防ぐのが nohupdisown、もっと根本的には tmux

nohup:起動時に SIGHUP を無視させる

$ nohup ./long_script.sh > output.log 2>&1 &
[1] 34567

nohup を付けて起動すると、シェル終了時の SIGHUP を無視する。ログアウトしてもジョブは生き残る。

disown:起動後に「シェルの管理下から外す」

$ ./long_script.sh &
[1] 45678
$ disown %1
$ exit   # ターミナルを閉じても %1 は生き続ける

& で動かし始めたあとに気が変わって長時間処理にしたい」場合は disown で OK。

より確実なのは tmux

nohup / disown は「ターミナル切断時にジョブが死なないようにする」だけ。一方 tmux は「画面ごとサーバ側に保存しておく」ので、後から tmux attach でその画面に戻って続きを見られる。長時間処理 + 後でログを見たいなら tmux 一択。→ tmux 入門

7. つまずきポイント集

リナ: 先輩、よくある落とし穴ってありますか?
ライニー先輩: 4 つ覚えておけばだいたい網羅できる。

ピンチ 1: Ctrl+C と Ctrl+Z を取り違える

症状: 一時停止のつもりだったのにジョブが消えた。

原因: Ctrl+C(SIGINT で終了)と Ctrl+Z(SIGTSTP で一時停止)を混同。

対処: 「C = Cancel(中止)、Z = Z 字に寝かせる(停止)」と覚える。Ctrl+Z は終了ではないので vim や editor で押しても作業内容は保たれる。

ピンチ 2: kill %1 を kill 1 と打って怖い思いをする

症状: kill 1 を実行して「Operation not permitted」と言われた。あるいは root で実行してシステムが止まりかけた。

原因: ジョブ番号と PID を取り違え、% を忘れた。kill 1 は PID 1(init / systemd)に終了シグナルを送る命令。

対処: ジョブ指定は必ず % を付ける。不安なら先に jobs -l で PID を確認するか、kill %%(最後にアクセスしたジョブ)を使う。

ピンチ 3: バックグラウンドジョブの出力が画面に乱入

症状: & で動かしたコマンドの出力が、他のコマンド入力中に画面に割り込んでくる。

原因: バックグラウンドでも標準出力・標準エラー出力はデフォルトで端末に流れる。

対処: > file 2>&1 & でファイルへ逃がす。/dev/null に捨てる場合は > /dev/null 2>&1 &

ピンチ 4: vim を bg したら止まったまま動かない

症状: vimCtrl+Zbg したけど、jobs で見ると Stopped (tty input) のまま動かない。

原因: vim は端末入力を待つアプリ。バックグラウンド(端末から切り離された状態)ではキー入力を受け付けられないため、シェルが自動的に停止状態に戻す。

対処: vim や top のような対話的フルスクリーンアプリは Ctrl+Z で停止 → 用が済んだら fg の往復だけで使う。bg には回さない。

8. 実用テンプレ:ジョブ制御の事故らない型

コピペ用:vim 編集中に別コマンドを打つ型

# 1. vim で編集中
$ vim config.txt
# Ctrl+Z で一時停止

# 2. 端末が戻ってきたので別作業
$ ls /etc/
$ grep "error" /var/log/syslog

# 3. vim に戻る
$ fg

vim を閉じずに往復するのが要。「Ctrl+Z → 別作業 → fg」のリズム。

コピペ用:長時間処理を裏で動かす型

# 出力をファイルへ逃がしながら裏で実行
$ ./long_script.sh > output.log 2>&1 &
[1] 12345

# 経過を確認
$ jobs
$ tail -f output.log

# 終了させたい場合
$ kill %1

> output.log 2>&1 を忘れると画面が荒れる。これとセットで覚える。

コピペ用:ログアウト後も継続させる型

# 起動時から nohup で
$ nohup ./long_script.sh > output.log 2>&1 &
[1] 12345

# あるいは起動後に disown
$ ./long_script.sh > output.log 2>&1 &
[1] 12345
$ disown %1

# tmux で画面ごと持ち越すのがより確実(推奨)
$ tmux
$ ./long_script.sh
# Ctrl+b → d でデタッチ、後で tmux attach で復帰

ミニ課題で手を動かそう

ライニー先輩: 知識だけ入れても定着しない。3 つ手を動かしてみよう。

課題 1: Ctrl+Z → fg の往復

$ sleep 30
# 5 秒経ったところで Ctrl+Z で一時停止

$ jobs
# → [1]+ Stopped sleep 30 を確認

$ ls    # 別コマンドを打ってみる

$ fg    # sleep に戻って残り時間を待つ

課題 2: 3 つのジョブを並行で動かす

$ sleep 100 &
$ sleep 200 &
$ sleep 300 &

$ jobs    # → [1] [2] [3] の 3 つが見えるはず

$ kill %2 # 真ん中だけ終了

$ jobs    # → [2] が消えていることを確認

課題 3: 出力をファイルへ逃がしながら裏で実行

$ (for i in 1 2 3 4 5; do echo "step $i"; sleep 1; done) > result.log 2>&1 &

$ jobs    # Running を確認
$ cat result.log   # ファイルへ出力が溜まっていく

# ジョブが終わったら自動的に
# [1]+  Done    ( for i in 1 2 3 4 5; ...) > result.log 2>&1
# と通知される
リナ: できました!Ctrl+Z の安心感、半端ないですね…これさえ知ってれば vim を Ctrl+C で殺すような事故は二度と起きない。
ライニー先輩: それが「ジョブ制御を覚える」って体験。シェルを使う日常が一段階楽になるよ。

今日の 3 行まとめ

  • Ctrl+Z で一時停止 → 端末解放、戻るときは fg、裏で動かすなら bg。これだけで vim を Ctrl+C で殺す事故が消える
  • ジョブ指定は必ず %kill %1kill 1)。PID と取り違えると init を殺しかねない
  • 長時間処理を裏で動かすなら & + > file 2>&1 を癖にして、ログアウトを跨ぐなら nohup / disown / tmux を選ぶ

次に学ぶこと