Linux bg命令

bg命令是Shell作业控制的一部分,用于将暂停的作业放入后台继续运行。通常与fgjobsCtrl+Z等命令和快捷键配合使用。
注意:bg命令仅适用于交互式Shell环境。在脚本中使用时需要注意作业控制的限制。

一、命令简介

bg命令(background的缩写)用于将当前Shell中暂停的作业(job)放到后台继续运行。它是Linux/Unix Shell作业控制系统的重要组成部分,与fgjobs等命令一起,为用户提供了强大的进程控制能力。

作业控制的主要命令:

  • jobs:列出当前Shell中的作业
  • bg:将作业放到后台运行
  • fg:将作业放到前台运行
  • Ctrl+Z:暂停前台作业
  • disown:将作业从作业表中移除

二、命令语法

bg [作业标识符]
bg %作业号

三、作业标识符

标识符 说明 示例
%n 作业号n(n为数字) bg %1bg %2
%+%% 当前作业(最近暂停的作业) bg %+bg %%
%- 上一个作业 bg %-
%string 以string开头的作业 bg %vi(以vi开头的作业)
%?string 包含string的作业 bg %?py(包含py的作业)
%-- 无参数时默认使用当前作业 bg(等价于bg %+

四、作业状态

状态符号 说明 含义
Running 作业正在运行 后台运行的作业
Stopped 作业已暂停 可以被bg/fg恢复
Done 作业已完成 正常结束
Killed 作业被终止 被信号终止
Terminated 作业被终止 被信号终止
Exit n 作业退出(退出码n) 非零退出码通常表示错误

五、使用示例

1. 基本用法

# 场景:暂停一个程序并放入后台

# 步骤1:启动一个长时间运行的程序
sleep 300

# 步骤2:按下 Ctrl+Z 暂停程序
# [1]+  Stopped                 sleep 300

# 步骤3:查看作业状态
jobs
# [1]+  Stopped                 sleep 300

# 步骤4:将作业放入后台继续运行
bg
# [1]+ sleep 300 &

# 或者使用作业号
bg %1

2. 多个作业管理

# 启动多个作业
sleep 1000
# Ctrl+Z: 暂停第一个作业
# [1]+  Stopped                 sleep 1000

sleep 2000
# Ctrl+Z: 暂停第二个作业
# [2]+  Stopped                 sleep 2000

# 查看所有作业
jobs
# [1]-  Stopped                 sleep 1000
# [2]+  Stopped                 sleep 2000

# 将作业1放入后台
bg %1
# [1]- sleep 1000 &

# 将作业2放入后台
bg %2
# [2]+ sleep 2000 &

# 再次查看作业状态
jobs
# [1]-  Running                 sleep 1000 &
# [2]+  Running                 sleep 2000 &

3. 编辑器的后台处理

# 使用vi/vim编辑文件时暂停并放入后台
vi document.txt
# 进入编辑器后,按下 Ctrl+Z 暂停
# [1]+  Stopped                 vi document.txt

# 放入后台继续运行(实际上编辑器会在后台暂停)
bg %1
# [1]+ vi document.txt &

# 回到前台继续编辑
fg %1

4. 编译任务的后台处理

# 编译大型项目时
make -j4

# 编译过程中按下 Ctrl+Z 暂停
# [1]+  Stopped                 make -j4

# 查看编译输出(如果需要)
tail -f compile.log

# 将编译任务放入后台继续
bg %1
# [1]+ make -j4 &

# 监控编译进度
jobs
# [1]+  Running                 make -j4 &

5. 结合重定向使用

# 启动一个输出到文件的作业
python data_processor.py > output.log 2>&1

# Ctrl+Z 暂停
# [1]+  Stopped                 python data_processor.py > output.log 2>&1

# 放入后台运行
bg
# [1]+ python data_processor.py > output.log 2>&1 &

# 监控输出文件
tail -f output.log

6. 使用作业名称标识符

# 启动多个不同类型的作业
python script1.py
# Ctrl+Z
# [1]+  Stopped                 python script1.py

python script2.py
# Ctrl+Z
# [2]+  Stopped                 python script2.py

bash backup.sh
# Ctrl+Z
# [3]+  Stopped                 bash backup.sh

# 使用包含特定字符串的标识符
bg %?script1     # 恢复包含"script1"的作业
bg %python       # 恢复以"python"开头的作业
bg %bash         # 恢复以"bash"开头的作业

7. 实际工作流程示例

# 完整的工作流程示例
# 1. 开始下载大文件
wget http://example.com/large-file.iso

# 2. 下载过程中需要暂停去做其他事情
# 按下 Ctrl+Z
# [1]+  Stopped                 wget http://example.com/large-file.iso

# 3. 查看下载进度(如果需要)
ls -lh large-file.iso

# 4. 将下载任务放入后台继续
bg
# [1]+ wget http://example.com/large-file.iso &

# 5. 做其他工作
vim notes.txt
# 编辑完成后保存退出

# 6. 检查下载状态
jobs
# [1]+  Running                 wget http://example.com/large-file.iso &

# 7. 如果需要监控进度
tail -f wget-log

# 8. 完成后将作业带回前台
fg %1
# 或者直接等待完成,然后按 Enter

8. 脚本中的作业控制

#!/bin/bash
# job_control_demo.sh

echo "演示Shell作业控制"

# 启动一个后台作业
sleep 60 &
echo "启动后台作业,PID: $!"

# 启动一个前台作业
echo "启动前台作业,10秒后按Ctrl+Z暂停"
sleep 10
# 这里脚本会暂停,需要手动操作

# 实际脚本中通常这样处理
echo "继续执行脚本..."
# 检查后台作业状态
jobs -l

六、实用技巧

技巧1:创建作业管理脚本
#!/bin/bash
# job_manager.sh - 作业管理工具

JOB_LOG="$HOME/.job_history.log"

log_job() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$JOB_LOG"
}

case "$1" in
    list)
        echo "当前作业列表:"
        jobs -l
        ;;

    start)
        shift
        CMD="$*"
        echo "启动作业: $CMD"

        # 在前台启动
        $CMD

        # 如果用户按了Ctrl+Z
        if [ $? -eq 130 ]; then
            log_job "作业暂停: $CMD"
            echo "作业已暂停,使用 'job_manager bg' 继续后台运行"
        fi
        ;;

    bg)
        if jobs | grep -q "Stopped"; then
            echo "将暂停的作业放入后台..."
            bg
            log_job "作业后台运行: $(jobs -l | grep Stopped | head -1)"
        else
            echo "没有暂停的作业"
        fi
        ;;

    fg)
        if jobs | grep -q "Stopped\|Running"; then
            echo "将作业带回前台..."
            fg
        else
            echo "没有可带回前台的作业"
        fi
        ;;

    history)
        echo "作业历史记录:"
        if [ -f "$JOB_LOG" ]; then
            cat "$JOB_LOG"
        else
            echo "暂无历史记录"
        fi
        ;;

    monitor)
        echo "监控作业状态..."
        watch -n 2 'jobs -l'
        ;;

    *)
        echo "用法: $0 {list|start [命令]|bg|fg|history|monitor}"
        exit 1
        ;;
esac
技巧2:自动后台任务监控
#!/bin/bash
# auto_background.sh - 自动后台任务监控

MONITOR_INTERVAL=5
LOG_FILE="/tmp/background_jobs.log"

monitor_jobs() {
    while true; do
        echo "=== $(date) ===" >> "$LOG_FILE"
        jobs -l >> "$LOG_FILE"
        echo "" >> "$LOG_FILE"

        # 检查是否有暂停的作业,自动放入后台
        STOPPED_JOBS=$(jobs -l | grep "Stopped" | awk '{print $2}')

        for job_pid in $STOPPED_JOBS; do
            echo "发现暂停的作业 PID: $job_pid,自动放入后台..." >> "$LOG_FILE"
            # 通过作业号放入后台
            job_num=$(jobs -l | grep "Stopped" | grep "$job_pid" | awk '{print $1}' | tr -d '[]+')
            bg %$job_num 2>/dev/null
        done

        sleep "$MONITOR_INTERVAL"
    done
}

# 启动监控(后台运行)
monitor_jobs &
MONITOR_PID=$!

echo "作业监控已启动,PID: $MONITOR_PID"
echo "日志文件: $LOG_FILE"
echo "停止监控请执行: kill $MONITOR_PID"

# 等待用户中断
trap "echo '停止监控...'; kill $MONITOR_PID; exit" INT TERM
wait
技巧3:作业资源限制
#!/bin/bash
# job_with_limits.sh - 带资源限制的作业控制

# 设置资源限制
ulimit -c unlimited      # 允许生成core文件
ulimit -n 1024          # 文件描述符限制
ulimit -u 512           # 用户进程数限制

echo "启动资源密集型作业..."

# 启动一个CPU密集型任务
calculate_primes() {
    echo "计算素数..."
    python3 -c "
import time
def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, int(n**0.5)+1):
        if n % i == 0:
            return False
    return True

count = 0
start = time.time()
for i in range(2, 1000000):
    if is_prime(i):
        count += 1
    # 每10万次检查一次是否需要暂停
    if i % 100000 == 0:
        print(f'进度: {i}/1000000, 找到素数: {count}')
end = time.time()
print(f'完成! 找到 {count} 个素数,耗时 {end-start:.2f} 秒')
"
}

# 在前台运行
calculate_primes

# 如果用户按Ctrl+Z暂停
if [ $? -eq 130 ]; then
    echo "作业已暂停"
    read -p "是否放入后台继续运行? (y/n): " -n 1 -r
    echo
    if [[ $REPLY =~ ^[Yy]$ ]]; then
        bg
        echo "作业已在后台运行,使用 'fg' 带回前台"
    fi
fi
技巧4:作业控制与进程管理结合
#!/bin/bash
# advanced_job_control.sh - 高级作业控制

cleanup() {
    echo "清理作业..."
    # 杀死所有后台作业
    jobs -p | xargs kill -9 2>/dev/null
    # 移除作业表
    disown -a 2>/dev/null
}

# 设置退出时清理
trap cleanup EXIT

# 启动多个类型的作业
start_jobs() {
    echo "启动测试作业..."

    # 作业1: 网络测试
    ping -c 100 google.com > ping.log 2>&1 &
    JOB1=$!
    echo "作业1 (网络测试) PID: $JOB1"

    # 作业2: 磁盘测试
    dd if=/dev/zero of=/tmp/test.dat bs=1M count=100 2>&1 &
    JOB2=$!
    echo "作业2 (磁盘测试) PID: $JOB2"

    # 作业3: CPU测试
    python3 -c "
import time
while True:
    for i in range(1000000):
        _ = i * i
    print('CPU测试运行中...', time.time())
    time.sleep(5)
" > cpu_test.log 2>&1 &
    JOB3=$!
    echo "作业3 (CPU测试) PID: $JOB3"
}

monitor_and_control() {
    echo "作业监控控制台"
    echo "命令:"
    echo "  l - 列出作业"
    echo "  s [n] - 暂停作业n"
    echo "  b [n] - 后台运行作业n"
    echo "  f [n] - 前台运行作业n"
    echo "  k [n] - 杀死作业n"
    echo "  q - 退出"

    while true; do
        echo -n "> "
        read cmd

        case "$cmd" in
            l)
                echo "当前作业:"
                jobs -l
                ;;
            s\ *)
                job_num=${cmd#s }
                kill -STOP %$job_num 2>/dev/null && echo "作业 $job_num 已暂停" || echo "操作失败"
                ;;
            b\ *)
                job_num=${cmd#b }
                bg %$job_num 2>/dev/null && echo "作业 $job_num 已放入后台" || echo "操作失败"
                ;;
            f\ *)
                job_num=${cmd#f }
                fg %$job_num 2>/dev/null && echo "作业 $job_num 已放入前台" || echo "操作失败"
                ;;
            k\ *)
                job_num=${cmd#k }
                kill -9 %$job_num 2>/dev/null && echo "作业 $job_num 已终止" || echo "操作失败"
                ;;
            q)
                echo "退出..."
                break
                ;;
            *)
                echo "未知命令"
                ;;
        esac
    done
}

# 主程序
start_jobs
monitor_and_control

七、bg vs & vs nohup

命令/符号 作用 特点 适用场景 示例
& 立即后台运行 启动时直接放入后台,终端关闭会收到SIGHUP 简单的后台任务,终端不关闭 sleep 100 &
bg 将暂停作业放入后台 需要先暂停作业(Ctrl+Z),然后放入后台 交互式任务管理,需要暂停/恢复控制 sleep 100Ctrl+Zbg
nohup 忽略挂起信号后台运行 终端关闭后继续运行,输出重定向到文件 长时间运行,需要终端无关的任务 nohup sleep 100 &
disown 从作业表移除 使作业不受Shell作业控制影响 使后台作业独立于Shell sleep 100 &disown
setsid 在新会话中运行 完全独立于终端 需要完全独立于当前Shell的任务 setsid sleep 100

八、注意事项

  • 交互式Shell限制:bg命令仅在交互式Shell中有效,脚本中默认禁用作业控制
  • 终端依赖:后台作业仍然与终端关联,终端关闭可能收到SIGHUP信号
  • 输出处理:后台作业的输出可能干扰前台工作,建议重定向到文件
  • 作业数量限制:系统对作业数量有限制,可用ulimit -u查看
  • Shell类型差异:不同Shell(bash、zsh、fish)的作业控制特性略有不同
  • 信号处理:某些程序可能不处理SIGTSTP(Ctrl+Z)信号
  • 脚本中使用:脚本中启用作业控制需要set -mset -o monitor
  • 网络程序:网络连接的后台作业在终端关闭时可能断开
  • 图形界面程序:GUI程序通常不应放入后台,可能导致界面问题

九、常见问题

&符号:

  • 在命令启动时直接放入后台
  • 命令立即开始执行,无需用户干预
  • 语法:命令 &
  • 示例:sleep 100 &

bg命令:

  • 将已经暂停的作业放入后台继续运行
  • 需要先暂停作业(Ctrl+Z),然后执行bg
  • 语法:bg [作业号]
  • 示例:sleep 100Ctrl+Zbg

工作流程对比:

# 使用&符号(一步到位)
$ sleep 100 &
[1] 12345  # 立即在后台运行
$ jobs
[1]+  Running                 sleep 100 &

# 使用bg命令(两步流程)
$ sleep 100  # 前台运行
# 按下 Ctrl+Z
[1]+  Stopped                 sleep 100
$ bg        # 放入后台继续
[1]+ sleep 100 &
$ jobs
[1]+  Running                 sleep 100 &

简单来说:&是"启动时就后台运行",bg是"先暂停,再后台恢复运行"。

有几种方法可以让后台作业在终端关闭后继续运行:

# 方法1: 使用nohup(最简单)
nohup sleep 1000 &
# 输出默认保存到 nohup.out

# 方法2: 使用disown
sleep 1000 &
disown  # 从作业表中移除,终端关闭不受影响

# 方法3: 使用setsid(在新会话中运行)
setsid sleep 1000

# 方法4: 使用screen或tmux(终端多路复用器)
screen -S mysession
sleep 1000
# Ctrl+A D 分离会话
# 重新连接: screen -r mysession

# 方法5: 使用systemd服务(生产环境推荐)
# 创建服务文件 /etc/systemd/system/mytask.service
# 然后: sudo systemctl start mytask.service

# 方法6: 使用at命令(计划任务)
echo "sleep 1000" | at now

# 方法7: 重定向输出并忽略挂起信号
(sleep 1000 > /dev/null 2>&1 &)

# 完整的保护脚本
protect_background() {
    # 启动任务
    "$@" &
    PID=$!

    # 忽略挂起信号
    disown $PID

    # 重定向输出
    echo "任务PID: $PID"
    echo "标准输出: /tmp/${PID}.out"
    echo "错误输出: /tmp/${PID}.err"

    # 实际的重定向(如果命令允许)
    # exec >/tmp/${PID}.out 2>/tmp/${PID}.err
}

# 使用
protect_background sleep 1000

脚本中默认禁用作业控制,需要显式启用:

#!/bin/bash
# job_control_in_script.sh

# 方法1: 启用作业控制
set -m  # 或 set -o monitor

echo "作业控制已启用"

# 启动一个后台作业
sleep 100 &
JOB1_PID=$!
echo "后台作业 PID: $JOB1_PID"

# 启动一个前台作业
sleep 200

# 在这个脚本中,不能直接使用Ctrl+Z
# 但可以通过信号模拟

# 方法2: 使用子Shell
(
    echo "在子Shell中启用作业控制"
    set -m
    sleep 300 &
    jobs
)

# 方法3: 使用coproc(协进程)
echo "使用协进程:"
coproc MY_PROC {
    sleep 400
    echo "协进程完成"
}

# 获取协进程的文件描述符
echo "协进程PID: ${MY_PROC_PID}"
echo "输入FD: ${MY_PROC[0]}"
echo "输出FD: ${MY_PROC[1]}"

# 方法4: 使用命名管道管理进程
echo "使用命名管道管理:"
PIPE="/tmp/mypipe.$$"
mkfifo "$PIPE"

# 启动后台进程,从管道读取命令
(
    while read cmd; do
        case "$cmd" in
            start) echo "启动任务..." ;;
            stop)  echo "停止任务..." ;;
            exit)  break ;;
        esac
    done < "$PIPE"
) &
PIPE_PROC=$!

# 发送命令到管道
echo "start" > "$PIPE"
sleep 1
echo "stop" > "$PIPE"
echo "exit" > "$PIPE"

# 清理
wait $PIPE_PROC
rm -f "$PIPE"

# 方法5: 使用文件锁控制作业
echo "使用文件锁控制:"
LOCK_FILE="/tmp/myjob.lock"

(
    # 获取锁
    exec 200>"$LOCK_FILE"
    flock -n 200 || { echo "作业已在运行"; exit 1; }

    echo "作业开始..."
    sleep 500
    echo "作业完成"

    # 释放锁
    flock -u 200
) &
JOB_PID=$!

echo "作业PID: $JOB_PID"

注意事项:

  • 脚本中的作业控制可能导致复杂的行为
  • 生产环境脚本通常避免使用作业控制
  • 考虑使用更专业的进程管理工具(如supervisord)
  • 确保正确处理信号和清理

有多种方法可以查看作业的详细信息:

# 基本方法
jobs      # 列出作业
jobs -l   # 列出作业和进程ID
jobs -p   # 只列出进程ID
jobs -n   # 只列出状态改变的作业

# 结合ps查看详细信息
jobs -l | awk '{print $2}' | xargs ps -f -p

# 使用进程状态
ps -o pid,pgid,sid,tty,state,cmd -p $(jobs -p)

# 查看作业的资源使用
top -p $(jobs -p | tr '\n' ',')

# 查看作业打开的文件
lsof -p $(jobs -p | tr '\n' ',')

# 完整的作业监控脚本
monitor_jobs() {
    echo "=== 作业监控 $(date) ==="
    echo

    # 作业列表
    echo "1. 作业列表:"
    jobs -l
    echo

    # 进程树
    echo "2. 进程树:"
    for pid in $(jobs -p); do
        pstree -p $pid
    done
    echo

    # 资源使用
    echo "3. 资源使用:"
    for pid in $(jobs -p); do
        echo "进程 $pid:"
        ps -o pid,ppid,pgid,sid,tty,stat,start_time,%cpu,%mem,cmd -p $pid
        echo
    done
    echo

    # 打开的文件
    echo "4. 打开的文件:"
    for pid in $(jobs -p); do
        echo "进程 $pid 打开的文件:"
        lsof -p $pid | head -5
        echo
    done
    echo

    # 网络连接
    echo "5. 网络连接:"
    for pid in $(jobs -p); do
        echo "进程 $pid 的网络连接:"
        ss -tup | grep "pid=$pid" || echo "  无网络连接"
        echo
    done
}

# 交互式监控
watch_jobs() {
    watch -n 2 '
        echo "作业状态:"
        jobs -l
        echo
        echo "进程信息:"
        for pid in $(jobs -p 2>/dev/null); do
            ps -o pid,user,%cpu,%mem,cmd --no-headers -p $pid
        done
    '
}

# 导出为HTML报告
jobs_to_html() {
    echo "<html><body>"
    echo "<h1>作业报告</h1>"
    echo "<pre>"
    jobs -l
    echo "</pre>"
    echo "</body></html>"
}

# 使用示例
monitor_jobs > job_report.txt
# 或者实时监控
# watch_jobs

十、最佳实践

bg命令使用最佳实践
  1. 明确作业状态:使用jobs命令确认作业状态后再操作
  2. 输出重定向:后台作业的输出应重定向到文件,避免干扰前台
  3. 作业标识:使用作业号而不是进程ID,因为作业号在Shell会话内稳定
  4. 终端无关:长时间运行的任务使用nohupdisown
  5. 资源监控:定期检查后台作业的资源使用情况
  6. 信号处理:了解不同信号对作业的影响(SIGTSTP、SIGCONT等)
  7. 清理作业:不再需要的作业应及时终止或清理
  8. 会话管理:对于复杂任务,考虑使用screentmux
  9. 生产环境:生产服务器使用systemd等专业进程管理工具
  10. 文档记录:重要的后台作业应有文档记录其目的和启动方式