fg、jobs、Ctrl+Z等命令和快捷键配合使用。
bg命令(background的缩写)用于将当前Shell中暂停的作业(job)放到后台继续运行。它是Linux/Unix Shell作业控制系统的重要组成部分,与fg、jobs等命令一起,为用户提供了强大的进程控制能力。
作业控制的主要命令:
jobs:列出当前Shell中的作业bg:将作业放到后台运行fg:将作业放到前台运行Ctrl+Z:暂停前台作业disown:将作业从作业表中移除bg [作业标识符]
bg %作业号
| 标识符 | 说明 | 示例 |
|---|---|---|
%n |
作业号n(n为数字) | bg %1、bg %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:启动一个长时间运行的程序
sleep 300
# 步骤2:按下 Ctrl+Z 暂停程序
# [1]+ Stopped sleep 300
# 步骤3:查看作业状态
jobs
# [1]+ Stopped sleep 300
# 步骤4:将作业放入后台继续运行
bg
# [1]+ sleep 300 &
# 或者使用作业号
bg %1
# 启动多个作业
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 &
# 使用vi/vim编辑文件时暂停并放入后台
vi document.txt
# 进入编辑器后,按下 Ctrl+Z 暂停
# [1]+ Stopped vi document.txt
# 放入后台继续运行(实际上编辑器会在后台暂停)
bg %1
# [1]+ vi document.txt &
# 回到前台继续编辑
fg %1
# 编译大型项目时
make -j4
# 编译过程中按下 Ctrl+Z 暂停
# [1]+ Stopped make -j4
# 查看编译输出(如果需要)
tail -f compile.log
# 将编译任务放入后台继续
bg %1
# [1]+ make -j4 &
# 监控编译进度
jobs
# [1]+ Running make -j4 &
# 启动一个输出到文件的作业
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
# 启动多个不同类型的作业
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"开头的作业
# 完整的工作流程示例
# 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
#!/bin/bash
# job_control_demo.sh
echo "演示Shell作业控制"
# 启动一个后台作业
sleep 60 &
echo "启动后台作业,PID: $!"
# 启动一个前台作业
echo "启动前台作业,10秒后按Ctrl+Z暂停"
sleep 10
# 这里脚本会暂停,需要手动操作
# 实际脚本中通常这样处理
echo "继续执行脚本..."
# 检查后台作业状态
jobs -l
#!/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
#!/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
#!/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
#!/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
| 命令/符号 | 作用 | 特点 | 适用场景 | 示例 |
|---|---|---|---|---|
| & | 立即后台运行 | 启动时直接放入后台,终端关闭会收到SIGHUP | 简单的后台任务,终端不关闭 | sleep 100 & |
| bg | 将暂停作业放入后台 | 需要先暂停作业(Ctrl+Z),然后放入后台 | 交互式任务管理,需要暂停/恢复控制 | sleep 100 → Ctrl+Z → bg |
| nohup | 忽略挂起信号后台运行 | 终端关闭后继续运行,输出重定向到文件 | 长时间运行,需要终端无关的任务 | nohup sleep 100 & |
| disown | 从作业表移除 | 使作业不受Shell作业控制影响 | 使后台作业独立于Shell | sleep 100 & → disown |
| setsid | 在新会话中运行 | 完全独立于终端 | 需要完全独立于当前Shell的任务 | setsid sleep 100 |
ulimit -u查看set -m或set -o monitor&符号:
命令 &sleep 100 &bg命令:
bg [作业号]sleep 100 → Ctrl+Z → bg工作流程对比:
# 使用&符号(一步到位)
$ 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"
注意事项:
有多种方法可以查看作业的详细信息:
# 基本方法
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
jobs命令确认作业状态后再操作nohup或disownscreen或tmux