bg、jobs、Ctrl+Z等命令配合使用,提供完整的进程控制能力。
fg命令(foreground的缩写)用于将后台运行的作业或暂停的作业恢复到前台继续执行。它是Linux/Unix Shell作业控制系统的核心组件,允许用户在多个任务间灵活切换,是交互式Shell环境中最重要的进程管理工具之一。
作业控制的核心命令:
jobs:列出当前Shell中的所有作业fg:将作业切换到前台运行bg:将作业切换到后台运行Ctrl+Z:暂停当前前台作业(发送SIGTSTP信号)Ctrl+C:终止当前前台作业(发送SIGINT信号)disown:从作业表中移除作业fg [作业标识符]
fg %作业号
| 标识符 | 说明 | 示例 |
|---|---|---|
%n |
作业号n(n为数字) | fg %1、fg %2 |
%+ 或 %% |
当前作业(最近使用的作业) | fg %+、fg %% |
%- |
上一个作业 | fg %- |
%string |
以string开头的作业 | fg %vi(切换到以vi开头的作业) |
%?string |
包含string的作业 | fg %?py(切换到包含py的作业) |
%-- |
无参数时默认使用当前作业 | fg(等价于fg %+) |
| 当前状态 | 执行fg后 |
说明 |
|---|---|---|
Running (后台) |
Running (前台) |
后台运行作业切换到前台继续运行 |
Stopped (后台) |
Running (前台) |
暂停的后台作业切换到前台并继续运行 |
Stopped (前台) |
Running (前台) |
暂停的前台作业恢复运行(不改变前后台状态) |
# 场景:将后台作业切换到前台
# 步骤1:启动一个后台作业
sleep 300 &
# [1] 12345
# 步骤2:查看作业状态
jobs
# [1]+ Running sleep 300 &
# 步骤3:将作业切换到前台
fg
# sleep 300
# 现在作业在前台运行,按Ctrl+C可终止
# 启动多个后台作业
sleep 1000 &
# [1] 12345
sleep 2000 &
# [2] 12346
# 查看所有作业
jobs
# [1]- Running sleep 1000 &
# [2]+ Running sleep 2000 &
# 将作业2切换到前台
fg %2
# sleep 2000
# 按Ctrl+Z暂停
# [2]+ Stopped sleep 2000
# 将作业1切换到前台
fg %1
# sleep 1000
# 场景:在编辑器和编译任务间切换
# 启动vim编辑文件
vim document.txt
# 编辑中...
# 按Ctrl+Z暂停vim
# [1]+ Stopped vim document.txt
# 启动编译任务
make -j4
# 编译输出...
# 按Ctrl+Z暂停编译
# [2]+ Stopped make -j4
# 查看所有作业
jobs
# [1]- Stopped vim document.txt
# [2]+ Stopped make -j4
# 切换回编辑器
fg %1
# 回到vim编辑器
# 保存文件后再次暂停
# :wq 或 Ctrl+Z
# 切换回编译任务
fg %2
# 继续编译
# 启动大文件下载
wget http://example.com/large.iso &
# [1] 12345
# 查看下载进度(后台作业)
jobs
# [1]+ Running wget http://example.com/large.iso &
# 如果需要查看详细输出,切换到前台
fg %1
# wget http://example.com/large.iso
# 显示下载进度...
# 按Ctrl+Z暂停下载
# [1]+ Stopped wget http://example.com/large.iso
# 做其他工作...
# 恢复下载(后台继续)
bg %1
# [1]+ wget http://example.com/large.iso &
# 再次切换到前台查看状态
fg %1
# 查看当前下载状态
# 启动不同类型的作业
python data_analysis.py &
# [1] 12345
tail -f /var/log/syslog &
# [2] 12346
vim notes.txt &
# [3] 12347
# 查看所有作业
jobs
# [1] Running python data_analysis.py &
# [2]- Running tail -f /var/log/syslog &
# [3]+ Running vim notes.txt &
# 使用名称标识符切换
fg %python # 切换到以python开头的作业
fg %?log # 切换到包含log的作业
fg %vim # 切换到以vim开头的作业
# 场景:在数据库操作和脚本编辑间切换
# 启动MySQL客户端
mysql -u root -p
# 进入MySQL交互模式
# 执行长时间查询时按Ctrl+Z暂停
# [1]+ Stopped mysql -u root -p
# 编辑SQL脚本
vim query.sql
# 编写查询语句...
# 按Ctrl+Z暂停编辑器
# [2]+ Stopped vim query.sql
# 切换回MySQL客户端
fg %1
# 回到MySQL,执行刚才编写的查询
# mysql> source query.sql;
# 查询执行中按Ctrl+Z暂停
# [1]+ Stopped mysql -u root -p
# 切换到编辑器修改脚本
fg %2
# 修改query.sql...
# 同时进行多个网络诊断
ping google.com > ping.log &
# [1] 12345
traceroute example.com > trace.log &
# [2] 12346
mtr yahoo.com > mtr.log &
# [3] 12347
# 查看所有诊断任务
jobs
# [1] Running ping google.com > ping.log &
# [2]- Running traceroute example.com > trace.log &
# [3]+ Running mtr yahoo.com > mtr.log &
# 切换到特定任务查看实时输出
fg %ping
# 查看ping的实时输出,按Ctrl+Z暂停
fg %traceroute
# 查看traceroute进度,按Ctrl+Z暂停
fg %mtr
# 查看mtr实时诊断,按Ctrl+C终止
# 调试复杂脚本时的任务切换
# 在后台运行测试脚本
./test_script.sh &
# [1] 12345
# 监控日志文件
tail -f test.log &
# [2] 12346
# 编辑脚本文件
vim test_script.sh
# 发现bug,修改...
# 按Ctrl+Z暂停编辑
# [3]+ Stopped vim test_script.sh
# 查看测试输出
fg %2
# tail -f test.log
# 查看错误信息...
# 按Ctrl+Z暂停日志监控
# [2]+ Stopped tail -f test.log
# 回到编辑器修复bug
fg %3
# 修复bug后保存退出
# 重新运行测试
./test_script.sh &
# [4] 12347
# 监控新日志
fg %2
# 继续监控日志...
#!/bin/bash
# smart_fg.sh - 智能作业切换工具
# 如果没有参数,尝试智能选择
if [ $# -eq 0 ]; then
# 获取所有作业
JOB_LIST=$(jobs -l)
if [ -z "$JOB_LIST" ]; then
echo "没有可用的作业"
exit 1
fi
# 如果有多个作业,让用户选择
if [ $(echo "$JOB_LIST" | wc -l) -gt 1 ]; then
echo "可用的作业:"
echo "$JOB_LIST"
echo
echo "请选择要切换到前台的作业:"
read -p "作业号 (或名称): " JOB_SPEC
if [[ "$JOB_SPEC" =~ ^[0-9]+$ ]]; then
# 数字作业号
fg %$JOB_SPEC
else
# 名称匹配
fg %$JOB_SPEC
fi
else
# 只有一个作业,直接切换
fg
fi
else
# 使用提供的参数
fg %$1
fi
#!/bin/bash
# job_history.sh - 作业历史记录和管理
HISTORY_FILE="$HOME/.job_history"
MAX_HISTORY=100
# 记录作业变化
record_job_change() {
local action="$1"
local job_info="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $action: $job_info" >> "$HISTORY_FILE"
# 限制历史记录大小
if [ $(wc -l < "$HISTORY_FILE") -gt $MAX_HISTORY ]; then
tail -n $MAX_HISTORY "$HISTORY_FILE" > "${HISTORY_FILE}.tmp"
mv "${HISTORY_FILE}.tmp" "$HISTORY_FILE"
fi
}
# 包装fg命令
smart_fg() {
# 获取当前作业信息
local job_info=$(jobs -l | grep "^\[$1\]" 2>/dev/null)
if [ -z "$job_info" ]; then
# 尝试其他匹配方式
job_info=$(jobs -l | grep "$1" | head -1)
fi
if [ -n "$job_info" ]; then
record_job_change "FG" "$job_info"
fg %$1
else
echo "未找到作业: $1"
return 1
fi
}
# 包装bg命令
smart_bg() {
local job_info=$(jobs -l | grep "^\[$1\]" 2>/dev/null)
if [ -z "$job_info" ]; then
job_info=$(jobs -l | grep "$1" | head -1)
fi
if [ -n "$job_info" ]; then
record_job_change "BG" "$job_info"
bg %$1
else
echo "未找到作业: $1"
return 1
fi
}
# 查看历史
show_history() {
if [ -f "$HISTORY_FILE" ]; then
echo "作业历史记录:"
cat "$HISTORY_FILE"
else
echo "暂无历史记录"
fi
}
# 主函数
case "$1" in
fg)
shift
smart_fg "$@"
;;
bg)
shift
smart_bg "$@"
;;
history)
show_history
;;
stats)
echo "作业统计:"
echo "当前作业数: $(jobs | wc -l)"
if [ -f "$HISTORY_FILE" ]; then
echo "历史记录数: $(wc -l < "$HISTORY_FILE")"
echo "最近操作:"
tail -5 "$HISTORY_FILE"
fi
;;
*)
echo "用法: $0 {fg [作业]|bg [作业]|history|stats}"
exit 1
;;
esac
#!/bin/bash
# job_recovery.sh - 作业自动恢复系统
LOG_FILE="/tmp/job_recovery.log"
RECOVERY_INTERVAL=30
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# 检查并恢复暂停的作业
check_and_recover_jobs() {
# 获取所有暂停的作业
local stopped_jobs=$(jobs -s)
if [ -n "$stopped_jobs" ]; then
log "发现暂停的作业:"
log "$stopped_jobs"
# 尝试自动恢复某些类型的作业
echo "$stopped_jobs" | while read line; do
local job_num=$(echo "$line" | sed -n 's/\[\([0-9]\+\)\].*/\1/p')
local job_cmd=$(echo "$line" | cut -d' ' -f3-)
# 根据命令类型决定是否自动恢复
case "$job_cmd" in
*download*|*wget*|*curl*|*rsync*)
log "恢复下载任务: $job_cmd"
bg %$job_num
;;
*make*|*compile*|*build*)
log "恢复编译任务: $job_cmd"
bg %$job_num
;;
*encode*|*convert*|*compress*)
log "恢复编码/转换任务: $job_cmd"
bg %$job_num
;;
*)
log "保持暂停状态: $job_cmd"
;;
esac
done
fi
# 检查长时间运行的前台作业
local running_jobs=$(jobs -r)
if [ -n "$running_jobs" ]; then
log "运行中的作业:"
log "$running_jobs"
fi
}
# 监控循环
monitor_loop() {
log "作业监控系统启动"
while true; do
check_and_recover_jobs
sleep "$RECOVERY_INTERVAL"
done
}
# 启动监控(后台运行)
start_monitor() {
monitor_loop &
MONITOR_PID=$!
log "监控进程PID: $MONITOR_PID"
echo $MONITOR_PID > /tmp/job_monitor.pid
}
# 停止监控
stop_monitor() {
if [ -f /tmp/job_monitor.pid ]; then
local pid=$(cat /tmp/job_monitor.pid)
kill $pid 2>/dev/null
rm -f /tmp/job_monitor.pid
log "监控已停止"
else
log "监控未运行"
fi
}
# 主程序
case "$1" in
start)
start_monitor
;;
stop)
stop_monitor
;;
status)
if [ -f /tmp/job_monitor.pid ]; then
local pid=$(cat /tmp/job_monitor.pid)
if ps -p $pid > /dev/null; then
log "监控运行中 (PID: $pid)"
else
log "监控进程已停止"
fi
else
log "监控未运行"
fi
;;
check)
check_and_recover_jobs
;;
*)
echo "用法: $0 {start|stop|status|check}"
exit 1
;;
esac
#!/bin/bash
# job_priority.sh - 作业优先级管理系统
# 作业优先级定义
declare -A PRIORITY_LEVELS=(
["high"]=0
["medium"]=1
["low"]=2
)
# 作业数据库
JOB_DB="$HOME/.job_priority.db"
# 初始化数据库
init_db() {
if [ ! -f "$JOB_DB" ]; then
cat > "$JOB_DB" << EOF
# 作业优先级数据库
# 格式: 作业标识|优先级|创建时间|最后访问
EOF
fi
}
# 记录作业
record_job() {
local job_id="$1"
local priority="${2:-medium}"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# 检查是否已存在
if grep -q "^$job_id|" "$JOB_DB" 2>/dev/null; then
# 更新最后访问时间
sed -i "/^$job_id|/s/|[^|]*$/|$timestamp/" "$JOB_DB"
else
# 新增记录
echo "$job_id|$priority|$timestamp|$timestamp" >> "$JOB_DB"
fi
}
# 获取作业优先级
get_job_priority() {
local job_id="$1"
local priority=$(grep "^$job_id|" "$JOB_DB" 2>/dev/null | cut -d'|' -f2)
echo "${priority:-medium}"
}
# 智能fg:根据优先级选择作业
priority_fg() {
init_db
# 获取当前作业
local jobs_list=$(jobs -l)
if [ -z "$jobs_list" ]; then
echo "没有可用的作业"
return 1
fi
# 解析作业信息
declare -a HIGH_JOBS
declare -a MEDIUM_JOBS
declare -a LOW_JOBS
while IFS= read -r line; do
local job_num=$(echo "$line" | sed -n 's/\[\([0-9]\+\)\].*/\1/p')
local job_pid=$(echo "$line" | awk '{print $2}')
local job_cmd=$(echo "$line" | cut -d' ' -f4-)
# 获取优先级
local priority=$(get_job_priority "$job_num")
# 记录到相应数组
case "$priority" in
high)
HIGH_JOBS+=("$job_num:$job_cmd")
;;
medium)
MEDIUM_JOBS+=("$job_num:$job_cmd")
;;
low)
LOW_JOBS+=("$job_num:$job_cmd")
;;
esac
# 更新访问记录
record_job "$job_num" "$priority"
done <<< "$jobs_list"
# 按优先级顺序选择作业
local selected_job
if [ ${#HIGH_JOBS[@]} -gt 0 ]; then
echo "高优先级作业:"
for job in "${HIGH_JOBS[@]}"; do
echo " ${job//:/ - }"
done
selected_job=$(echo "${HIGH_JOBS[0]}" | cut -d: -f1)
elif [ ${#MEDIUM_JOBS[@]} -gt 0 ]; then
echo "中优先级作业:"
for job in "${MEDIUM_JOBS[@]}"; do
echo " ${job//:/ - }"
done
selected_job=$(echo "${MEDIUM_JOBS[0]}" | cut -d: -f1)
elif [ ${#LOW_JOBS[@]} -gt 0 ]; then
echo "低优先级作业:"
for job in "${LOW_JOBS[@]}"; do
echo " ${job//:/ - }"
done
selected_job=$(echo "${LOW_JOBS[0]}" | cut -d: -f1)
fi
if [ -n "$selected_job" ]; then
echo "切换到作业: $selected_job"
fg %$selected_job
fi
}
# 设置作业优先级
set_priority() {
init_db
local job_id="$1"
local priority="$2"
if [ -z "$job_id" ] || [ -z "$priority" ]; then
echo "用法: $0 set <作业号> "
return 1
fi
if [[ ! "${!PRIORITY_LEVELS[@]}" =~ $priority ]]; then
echo "无效的优先级: $priority"
return 1
fi
record_job "$job_id" "$priority"
echo "作业 $job_id 的优先级已设置为 $priority"
}
# 查看作业优先级
show_priorities() {
init_db
echo "作业优先级列表:"
echo "作业号 | 优先级 | 创建时间 | 最后访问"
echo "-------------------------------------"
# 获取当前所有作业
local current_jobs=$(jobs -l | awk '{print $1}' | tr -d '[]+')
for job in $current_jobs; do
local record=$(grep "^$job|" "$JOB_DB" 2>/dev/null)
if [ -n "$record" ]; then
echo "$record" | tr '|' ' '
else
echo "$job | medium | (未记录) | (未记录)"
fi
done
}
# 主程序
case "$1" in
fg)
priority_fg
;;
set)
shift
set_priority "$@"
;;
show)
show_priorities
;;
*)
echo "用法: $0 {fg|set <作业> <优先级>|show}"
exit 1
;;
esac
| 命令/操作 | 功能 | 常用场景 | 工作流程 |
|---|---|---|---|
| fg | 将作业切换到前台运行 | 需要与作业交互、查看输出、输入数据 | 后台/暂停 → 前台运行 |
| bg | 将作业切换到后台运行 | 让作业在后台继续执行,不占用终端 | 暂停 → 后台运行 |
| & | 启动时直接后台运行 | 不需要交互的长时间任务 | 直接后台运行 |
| Ctrl+Z | 暂停前台作业 | 临时中断作业去做其他事 | 前台运行 → 暂停 |
| Ctrl+C | 终止前台作业 | 需要立即停止作业 | 前台运行 → 终止 |
命令 &或直接运行命令Ctrl+Z发送SIGTSTP信号bg命令发送SIGCONT信号fg命令发送SIGCONT信号Ctrl+C或使用kill命令disown从作业表移除set -mulimit -u查看如果fg命令提示找不到作业,可能有以下几种原因和解决方法:
# 1. 使用jobs命令查看当前作业
jobs
# 输出示例:
# [1] Running sleep 1000 &
# [2]- Stopped vim file.txt
# 2. 检查作业号是否正确
# 错误的作业号
fg %99 # 如果作业99不存在
# 正确的作业号
fg %1 # 使用jobs显示的作业号
# 3. 作业可能已经终止
# 如果作业已经完成或被杀死,会从作业表中移除
# 4. 在不同的Shell中
# 作业是Shell特定的,每个Shell有自己的作业表
# 在一个Shell中启动的作业不能在另一个Shell中控制
# 5. 脚本中默认禁用作业控制
# 在脚本中使用需要显式启用
set -m # 启用作业控制
# 6. 使用完整的作业标识符
fg # 切换到当前作业(最近使用的作业)
fg %% # 同上,切换到当前作业
fg %+ # 同上
fg %- # 切换到上一个作业
# 7. 使用名称匹配
fg %sleep # 切换到以sleep开头的作业
fg %?txt # 切换到包含txt的作业
# 8. 作业可能被disown了
# disown命令会从作业表中移除作业
# 被disown的作业无法用fg/bg控制
# 9. 检查Shell是否支持作业控制
echo $-
# 如果输出包含"m",则支持作业控制
# 10. 完整的作业查找脚本
find_job() {
local pattern="$1"
# 使用jobs查找
local job_info=$(jobs -l | grep -i "$pattern")
if [ -z "$job_info" ]; then
echo "未找到匹配的作业: $pattern"
return 1
fi
echo "找到作业:"
echo "$job_info"
# 提取作业号
local job_num=$(echo "$job_info" | head -1 | sed -n 's/\[\([0-9]\+\)\].*/\1/p')
if [ -n "$job_num" ]; then
echo "切换到作业 $job_num"
fg %$job_num
fi
}
# 使用示例
find_job "sleep"
find_job "vim"
有几种方法可以精确切换到特定的后台作业:
# 方法1: 使用作业号(最精确)
# 先查看所有作业及其编号
jobs -l
# [1] 12345 Running sleep 1000 &
# [2]- 12346 Stopped vim file.txt
# [3]+ 12347 Running tail -f log.txt &
# 切换到作业2
fg %2
# 方法2: 使用进程ID(需要额外步骤)
# 查找特定进程的作业号
find_job_by_pid() {
local pid="$1"
local job_info=$(jobs -l | grep " $pid ")
if [ -n "$job_info" ]; then
local job_num=$(echo "$job_info" | sed -n 's/\[\([0-9]\+\)\].*/\1/p')
echo "找到作业: $job_num (PID: $pid)"
fg %$job_num
else
echo "未找到PID为 $pid 的作业"
fi
}
# 使用示例
find_job_by_pid 12346
# 方法3: 使用命令名匹配
# 切换到以特定字符串开头的作业
fg %sleep # 切换到以sleep开头的作业
fg %vim # 切换到以vim开头的作业
# 切换到包含特定字符串的作业
fg %?file # 切换到包含file的作业
fg %?log # 切换到包含log的作业
# 方法4: 使用作业状态过滤
# 只切换到运行中的作业
fg_running() {
local running_jobs=$(jobs -r)
if [ -z "$running_jobs" ]; then
echo "没有运行中的作业"
return 1
fi
# 如果有多个运行中的作业,选择第一个
local job_num=$(echo "$running_jobs" | head -1 | sed -n 's/\[\([0-9]\+\)\].*/\1/p')
fg %$job_num
}
# 只切换到暂停的作业
fg_stopped() {
local stopped_jobs=$(jobs -s)
if [ -z "$stopped_jobs" ]; then
echo "没有暂停的作业"
return 1
fi
local job_num=$(echo "$stopped_jobs" | head -1 | sed -n 's/\[\([0-9]\+\)\].*/\1/p')
fg %$job_num
}
# 方法5: 交互式选择
interactive_fg() {
# 获取作业列表
local job_list=$(jobs -l)
if [ -z "$job_list" ]; then
echo "没有可用的作业"
return 1
fi
# 显示作业菜单
echo "请选择要切换到前台的作业:"
echo "$job_list" | cat -n
# 读取用户选择
read -p "请输入编号: " choice
# 提取作业号
local job_line=$(echo "$job_list" | sed -n "${choice}p")
local job_num=$(echo "$job_line" | sed -n 's/\[\([0-9]\+\)\].*/\1/p')
if [ -n "$job_num" ]; then
fg %$job_num
else
echo "无效的选择"
fi
}
# 方法6: 最近使用的作业
# fg(无参数)切换到当前作业(最近使用的作业)
fg
# 或
fg %+
fg %%
# 上一个作业
fg %-
脚本中默认禁用作业控制,需要显式启用:
#!/bin/bash
# fg_in_script.sh
# 方法1: 启用作业控制
set -m # 启用作业控制
# 或
set -o monitor
echo "作业控制已启用"
# 启动后台作业
sleep 100 &
JOB1_PID=$!
echo "后台作业1 PID: $JOB1_PID"
sleep 200 &
JOB2_PID=$!
echo "后台作业2 PID: $JOB2_PID"
# 查看作业
jobs -l
# 将作业1切换到前台
fg %1
# 注意:这会使脚本暂停,直到作业1完成或再次被暂停
# 如果作业在前台运行,脚本会阻塞在这里
# 可以按Ctrl+Z暂停,然后脚本继续执行
echo "脚本继续执行..."
# 方法2: 使用子Shell启用作业控制
(
echo "在子Shell中启用作业控制"
set -m
# 启动作业
sleep 300 &
# 查看作业
jobs -l
# 切换到前台
fg %1
echo "子Shell中的作业完成"
)
# 方法3: 使用协进程(coproc)
echo "使用协进程:"
coproc MYPROC {
echo "协进程开始"
sleep 5
echo "协进程结束"
}
# 获取协进程信息
echo "协进程PID: ${MYPROC_PID}"
echo "输入FD: ${MYPROC[0]}"
echo "输出FD: ${MYPROC[1]}"
# 读取协进程输出
read -r output <&"${MYPROC[0]}"
echo "协进程输出: $output"
# 方法4: 使用命名管道控制
CONTROL_PIPE="/tmp/control_pipe.$$"
mkfifo "$CONTROL_PIPE"
# 启动受控进程
(
echo "受控进程启动"
# 从管道读取控制命令
while read -r cmd; do
case "$cmd" in
"fg")
echo "切换到前台模式"
# 实际的前台处理逻辑
;;
"bg")
echo "切换到后台模式"
;;
"pause")
echo "暂停"
;;
"resume")
echo "恢复"
;;
"exit")
echo "退出"
break
;;
esac
done < "$CONTROL_PIPE"
) &
CONTROLLED_PID=$!
# 发送控制命令
echo "fg" > "$CONTROL_PIPE"
sleep 1
echo "pause" > "$CONTROL_PIPE"
sleep 1
echo "resume" > "$CONTROL_PIPE"
sleep 1
echo "exit" > "$CONTROL_PIPE"
# 等待进程结束
wait $CONTROLLED_PID
# 清理
rm -f "$CONTROL_PIPE"
# 方法5: 使用文件锁和状态文件
STATE_FILE="/tmp/job_state.$$"
# 启动工作进程
(
# 初始化状态
echo "running" > "$STATE_FILE"
trap 'echo "paused" > "$STATE_FILE"; exit 0' SIGTSTP
trap 'echo "terminated" > "$STATE_FILE"; exit 0' SIGINT
echo "工作进程运行中..."
sleep 10
echo "工作进程完成"
) &
WORKER_PID=$!
# 控制循环
while true; do
# 读取状态
state=$(cat "$STATE_FILE" 2>/dev/null || echo "unknown")
case "$state" in
"running")
echo "工作进程运行中"
;;
"paused")
echo "工作进程已暂停"
read -p "恢复执行? (y/n): " choice
if [[ "$choice" == "y" ]]; then
kill -CONT $WORKER_PID
echo "running" > "$STATE_FILE"
fi
;;
"terminated")
echo "工作进程已终止"
break
;;
*)
echo "未知状态: $state"
break
;;
esac
sleep 2
done
rm -f "$STATE_FILE"
脚本中使用fg的注意事项:
fg和Ctrl+Z组合使用可以实现强大的作业控制:
# 技巧1: 快速任务切换
# 启动任务A
vim file1.txt
# 按Ctrl+Z暂停
# [1]+ Stopped vim file1.txt
# 启动任务B
python script.py
# 按Ctrl+Z暂停
# [2]+ Stopped python script.py
# 快速切换回任务A
fg %1
# 或 fg %vim
# 技巧2: 临时中断处理
# 长时间运行的任务
./long_running_task.sh
# 运行中需要临时处理其他事情
# 按Ctrl+Z暂停
# [1]+ Stopped ./long_running_task.sh
# 处理临时任务
quick_check.sh
# 返回原任务
fg
# 或 fg %1
# 技巧3: 多任务轮转
# 启动多个任务
task1.sh &
task2.sh &
task3.sh &
# 轮流切换到前台查看状态
fg %1
# 查看任务1,按Ctrl+Z暂停
fg %2
# 查看任务2,按Ctrl+Z暂停
fg %3
# 查看任务3,按Ctrl+Z暂停
# 技巧4: 安全保存状态
# 重要编辑任务
vim important_doc.txt
# 临时需要离开,安全保存
# 在vim中按Ctrl+Z(保存文件并暂停vim)
# [1]+ Stopped vim important_doc.txt
# 可以安全关闭终端,稍后恢复
# 重新打开终端后,如果有作业会提示
# 有停止的作业
# 恢复编辑
fg
# 或 vim important_doc.txt
# 技巧5: 调试循环
debug_cycle() {
# 启动被调试的程序
./buggy_program &
DEBUG_PID=$!
while true; do
# 等待程序运行
sleep 2
# 暂停程序进行调试
kill -TSTP $DEBUG_PID
# 交互调试
echo "程序已暂停,进行调试..."
read -p "调试命令 (c=继续, q=退出): " cmd
case "$cmd" in
c)
# 继续执行
kill -CONT $DEBUG_PID
;;
q)
# 退出调试
kill -TERM $DEBUG_PID
break
;;
*)
echo "未知命令"
;;
esac
done
}
# 技巧6: 资源监控模式
resource_monitor() {
# 启动资源密集型任务
./resource_hungry_task.sh &
TASK_PID=$!
# 监控循环
while kill -0 $TASK_PID 2>/dev/null; do
# 暂停任务进行检查
kill -TSTP $TASK_PID
# 检查资源使用
echo "=== 资源检查 ==="
ps -p $TASK_PID -o pid,%cpu,%mem,cmd
echo
# 等待用户决定
read -p "继续执行? (y/n/p=暂停): " choice
case "$choice" in
y)
# 继续执行
kill -CONT $TASK_PID
sleep 5 # 运行5秒后再检查
;;
n)
# 终止任务
kill -TERM $TASK_PID
break
;;
p)
# 保持暂停状态
echo "任务保持暂停状态"
break
;;
esac
done
}
# 技巧7: 批量作业控制
batch_control() {
# 启动一批作业
for i in {1..5}; do
./job_$i.sh &
echo "作业 $i 启动: $!"
done
# 批量控制
while true; do
echo "当前作业状态:"
jobs -l
echo
echo "控制选项:"
echo "1. 将所有作业切换到前台"
echo "2. 将所有作业暂停"
echo "3. 恢复所有暂停的作业"
echo "4. 终止所有作业"
echo "5. 退出"
read -p "选择: " option
case "$option" in
1)
# 将所有作业依次切换到前台
for job in $(jobs -p); do
fg %$(jobs -l | grep " $job " | sed 's/\[\([0-9]\+\).*/\1/')
# 按Ctrl+Z暂停当前作业,继续下一个
done
;;
2)
# 暂停所有运行中的作业
for job in $(jobs -rp); do
kill -TSTP $job
done
;;
3)
# 恢复所有暂停的作业
for job in $(jobs -sp); do
kill -CONT $job
done
;;
4)
# 终止所有作业
for job in $(jobs -p); do
kill -TERM $job
done
break
;;
5)
break
;;
esac
done
}
jobs -l查看作业详情后再操作screen或tmux管理