Skip to content

作业控制

作业控制(Job Control)允许用户在一个终端中管理多个进程,在前台和后台之间切换。

基本概念

  • 前台作业:独占终端,用户需等待其完成才能继续输入
  • 后台作业:在后台运行,不独占终端,用户可以继续操作
  • 作业编号:Shell 为每个后台作业分配的编号(如 [1]

后台运行

bash
# 在命令末尾加 & 使其在后台运行
sleep 60 &
# 输出:[1] 12345   ([作业编号] PID)

# 后台运行并忽略输出
sleep 60 > /dev/null 2>&1 &

# 获取最后一个后台进程的 PID
echo "后台进程 PID: $!"

查看作业

bash
# 列出当前 Shell 的所有作业
jobs

# 显示 PID
jobs -l

# 只显示运行中的作业
jobs -r

# 只显示已停止的作业
jobs -s

输出示例:

[1]   Running    sleep 60 &
[2]-  Stopped    vim file.txt
[3]+  Running    ping google.com > /dev/null &
  • + 表示最近的作业(fg/bg 默认操作对象)
  • - 表示倒数第二个作业

前台与后台切换

bash
# 挂起前台进程(Ctrl+Z)
# 按下 Ctrl+Z 将当前前台进程暂停并放入后台

# 将挂起的作业放到后台继续运行
bg          # 操作最近的作业
bg %1       # 操作编号为 1 的作业

# 将后台作业调到前台
fg          # 操作最近的作业(带 + 号的)
fg %1       # 操作编号为 1 的作业
fg %2       # 操作编号为 2 的作业

作业编号引用

bash
%n        # 作业编号 n
%+        # 最近的作业
%-        # 倒数第二个作业
%string   # 命令以 string 开头的作业
%?string  # 命令包含 string 的作业
bash
# 示例
sleep 100 &    # [1] 12345
sleep 200 &    # [2] 12346
vim file.txt   # 按 Ctrl+Z 挂起 → [3] 12347

jobs -l
# [1]  12345 Running    sleep 100 &
# [2]- 12346 Running    sleep 200 &
# [3]+ 12347 Stopped    vim file.txt

fg %vim        # 恢复 vim
kill %1        # 终止作业 1

终止作业

bash
# 通过作业编号发送信号
kill %1        # 发送 SIGTERM
kill -9 %2     # 发送 SIGKILL

# 等待特定作业完成
wait %1
wait           # 等待所有后台作业完成

nohup — 忽略挂起信号

退出终端时,Shell 会向所有作业发送 SIGHUP,导致后台进程也退出。使用 nohup 可以防止这种情况:

bash
# 使用 nohup 运行,退出终端后进程继续运行
nohup ./long_script.sh &

# 输出默认写入 nohup.out
nohup ./long_script.sh > mylog.txt 2>&1 &

# 查看 nohup 进程
ps aux | grep long_script

disown — 从 Shell 作业表中移除

bash
# 先将进程放入后台
sleep 1000 &    # [1] 12345

# 从作业表中移除(不再受 SIGHUP 影响)
disown %1

# 移除所有作业
disown -a

# 移除并抑制 SIGHUP
disown -h %1

实用场景

并行执行多个任务

bash
#!/bin/bash

# 并行执行,等待全部完成
task1() { sleep 3; echo "任务1完成"; }
task2() { sleep 2; echo "任务2完成"; }
task3() { sleep 4; echo "任务3完成"; }

task1 &
task2 &
task3 &

wait    # 等待所有后台任务完成
echo "全部任务完成"

带超时的后台任务

bash
#!/bin/bash

run_with_timeout() {
    local timeout=$1
    local cmd="${@:2}"

    $cmd &
    local pid=$!

    sleep "$timeout" &
    local sleep_pid=$!

    # 等待任意一个完成
    wait -n 2>/dev/null

    if kill -0 "$pid" 2>/dev/null; then
        echo "超时,终止进程 $pid"
        kill "$pid"
    fi

    kill "$sleep_pid" 2>/dev/null
    wait
}

run_with_timeout 5 sleep 100