fg、bg等命令进行作业管理的基础。
disown或nohup)。
jobs命令用于列出当前Shell会话中的所有作业及其状态。作业可以是后台运行的进程,也可以是被暂停(通过Ctrl+Z)的进程。jobs命令是Linux/Unix作业控制系统的重要组成部分,为用户提供了监控和管理并发任务的强大能力。
作业控制系统的完整工作流:
jobs:查看当前作业状态&:将命令放到后台运行Ctrl+Z:暂停前台作业fg:将作业切换到前台bg:将作业切换到后台kill %n:终止指定作业disown:将作业从作业表中移除jobs [选项] [作业标识符...]
jobs -l
jobs -p
| 选项 | 说明 |
|---|---|
-l |
长格式输出,显示作业的PID |
-n |
仅显示自上次通知后状态发生改变的作业 |
-p |
仅显示作业的进程ID(PID) |
-r |
仅显示运行中的作业(running jobs) |
-s |
仅显示暂停的作业(stopped jobs) |
-x |
用指定的命令替换作业ID并执行 |
[1] Running sleep 1000 &
[2]- Stopped vim document.txt
[3]+ Running tail -f /var/log/syslog &
各部分的含义:
[1]:作业编号,使用%1引用该作业+:当前作业(最近被放入后台或从前台暂停的作业)-:上一个作业Running:作业状态(运行中)Stopped:作业状态(已暂停)sleep 1000 &:作业命令| 状态 | 说明 | 产生原因 | 可执行的操作 |
|---|---|---|---|
Running |
作业正在运行(后台) | 使用&启动,或bg恢复 |
fg、kill |
Stopped |
作业已暂停 | 按下Ctrl+Z暂停前台作业 |
fg、bg、kill |
Done |
作业已完成 | 进程正常退出 | 无(自动从列表移除) |
Killed |
作业被终止 | 收到SIGKILL等信号 |
无 |
Terminated |
作业被终止 | 收到SIGTERM等信号 |
无 |
Exit n |
作业退出(退出码n) | 进程以非零退出码结束 | 检查错误原因 |
# 启动几个后台作业
sleep 1000 &
# [1] 12345
sleep 2000 &
# [2] 12346
# 查看所有作业
jobs
# [1]- Running sleep 1000 &
# [2]+ Running sleep 2000 &
# 启动前台作业并暂停
sleep 3000
# 按下 Ctrl+Z
# [3]+ Stopped sleep 3000
# 再次查看作业
jobs
# [1] Running sleep 1000 &
# [2]- Running sleep 2000 &
# [3]+ Stopped sleep 3000
# 启动几个作业
sleep 500 &
vim file.txt &
# 按Ctrl+Z暂停vim
# 显示详细作业信息
jobs -l
# [1] 12345 Running sleep 500 &
# [2]- 12346 Stopped vim file.txt
# [3]+ 12347 Running tail -f log.log &
# 字段说明:
# [1] 作业编号
# 12345 进程ID(PID)
# Running 作业状态
# sleep 500 & 执行的命令
# 启动不同类型的作业
sleep 1000 &
python script.py
# 按Ctrl+Z暂停
vim file.txt &
make -j4
# 按Ctrl+Z暂停
# 仅显示运行中的作业
jobs -r
# [1] Running sleep 1000 &
# [4] Running vim file.txt &
# 仅显示暂停的作业
jobs -s
# [2] Stopped python script.py
# [3] Stopped make -j4
# 显示所有作业(默认)
jobs
# [1] Running sleep 1000 &
# [2]- Stopped python script.py
# [3]+ Stopped make -j4
# [4] Running vim file.txt &
# 启动几个作业
sleep 1000 &
sleep 2000 &
sleep 3000 &
# 只显示进程ID
jobs -p
# 12345
# 12346
# 12347
# 结合其他命令使用
# 杀死所有后台作业
jobs -p | xargs kill
# 查看作业的资源使用情况
jobs -p | xargs top -p
# 查看作业打开的文件
jobs -p | xargs -I {} lsof -p {}
# 启动一个作业
sleep 1000 &
# [1] 12345
# 初始查看
jobs -n
# 不会显示任何输出,因为没有状态变化
# 暂停一个作业
sleep 2000
# 按Ctrl+Z
# [2]+ Stopped sleep 2000
# 查看状态变化的作业
jobs -n
# [2]+ Stopped sleep 2000
# 将作业放入后台
bg %2
# [2]+ sleep 2000 &
# 再次查看状态变化的作业
jobs -n
# [2]+ Running sleep 2000 &
# 启动几个作业
sleep 1000 &
sleep 2000 &
# 查看所有作业
jobs
# [1]- Running sleep 1000 &
# [2]+ Running sleep 2000 &
# 使用-x选项替换执行命令
# 显示作业1的详细信息
jobs -x ps -f %1
# UID PID PPID C STIME TTY TIME CMD
# user 12345 6789 0 14:30 pts/0 00:00:00 sleep 1000
# 杀死所有运行中的作业
jobs -r -x kill %%
# 这会向所有运行中的作业发送TERM信号
# 重新启动暂停的作业
jobs -s -x bg %%
# 将所有暂停的作业放入后台继续运行
# 1. 查看作业的完整进程树
jobs -l | awk '{print $2}' | xargs -I {} pstree -p {}
# 2. 监控作业的资源使用
watch -n 1 'jobs -l; echo; jobs -p | xargs -r ps -o pid,user,%cpu,%mem,cmd -p'
# 3. 根据作业名筛选
jobs -l | grep "python"
# 4. 统计各种状态的作业数量
echo "运行中: $(jobs -r | wc -l)"
echo "暂停中: $(jobs -s | wc -l)"
echo "总计: $(jobs | wc -l)"
# 5. 自动管理作业(脚本中)
# 检查是否有作业运行,没有则启动
if [ $(jobs -r | wc -l) -eq 0 ]; then
echo "没有运行中的作业,启动新任务..."
./start_tasks.sh &
fi
# 场景1:开发环境中的多任务管理
# 启动开发服务器
python manage.py runserver &
# [1] 12345
# 启动前端构建
npm run watch &
# [2] 12346
# 启动数据库监控
watch -n 5 "psql -c 'SELECT count(*) FROM users;'" &
# [3] 12347
# 查看所有开发任务状态
jobs -l
# [1] 12345 Running python manage.py runserver &
# [2] 12346 Running npm run watch &
# [3]+ 12347 Running watch -n 5 "psql -c 'SELECT count(*) FROM users;'" &
# 场景2:系统管理任务监控
# 启动备份任务
tar -czf backup.tar.gz /important/data &
# [1] 12345
# 启动日志轮转
logrotate -f /etc/logrotate.conf &
# [2] 12346
# 启动系统更新
apt-get update && apt-get upgrade -y &
# [3] 12347
# 定期检查任务状态
while true; do
clear
echo "=== 系统任务监控 ==="
date
echo
jobs -l
echo
echo "按Ctrl+C退出监控"
sleep 10
done
#!/bin/bash
# job_monitor.sh - 实时作业监控面板
MONITOR_INTERVAL=2 # 更新间隔(秒)
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 清除屏幕并显示监控面板
show_monitor() {
clear
echo -e "${BLUE}=== 作业监控面板 ===${NC}"
echo -e "时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo -e "用户: $(whoami)"
echo -e "终端: $(tty)"
echo
# 获取作业信息
local jobs_output=$(jobs -l)
local running_count=$(jobs -r | wc -l)
local stopped_count=$(jobs -s | wc -l)
local total_count=$(jobs | wc -l)
# 显示统计信息
echo -e "${GREEN}运行中: ${running_count}${NC} | ${YELLOW}暂停中: ${stopped_count}${NC} | 总计: ${total_count}"
echo "----------------------------------------"
if [ -z "$jobs_output" ]; then
echo "没有活动的作业"
else
# 高亮显示作业状态
echo "$jobs_output" | while IFS= read -r line; do
if [[ "$line" == *"Running"* ]]; then
echo -e "${GREEN}$line${NC}"
elif [[ "$line" == *"Stopped"* ]]; then
echo -e "${YELLOW}$line${NC}"
elif [[ "$line" == *"Done"* ]] || [[ "$line" == *"Exit"* ]] || [[ "$line" == *"Killed"* ]]; then
echo -e "${RED}$line${NC}"
else
echo "$line"
fi
done
fi
echo
echo "----------------------------------------"
echo -e "${BLUE}系统资源:${NC}"
# 显示系统资源使用
if [ $total_count -gt 0 ]; then
echo "作业CPU使用:"
jobs -p | xargs -r ps -o pid,%cpu,cmd --no-headers -p 2>/dev/null | head -5
echo
echo "作业内存使用:"
jobs -p | xargs -r ps -o pid,%mem,cmd --no-headers -p 2>/dev/null | head -5
fi
echo
echo -e "${BLUE}快捷键:${NC}"
echo -e " ${GREEN}Enter${NC} - 刷新 ${YELLOW}q${NC} - 退出 ${RED}k${NC} - 杀死当前作业"
echo -e " ${BLUE}f${NC} - 前台运行 ${BLUE}b${NC} - 后台运行"
}
# 处理用户输入
handle_input() {
read -t 0.1 -n 1 input # 非阻塞读取单个字符
if [ -n "$input" ]; then
case "$input" in
q)
echo "退出监控..."
exit 0
;;
k)
# 杀死当前作业(带+号的作业)
current_job=$(jobs | grep '+ ' | head -1 | sed -n 's/\[\([0-9]\+\)\]+.*/\1/p')
if [ -n "$current_job" ]; then
kill -9 %$current_job 2>/dev/null
echo "已杀死作业 $current_job"
sleep 1
fi
;;
f)
# 将当前作业切换到前台
current_job=$(jobs | grep '+ ' | head -1 | sed -n 's/\[\([0-9]\+\)\]+.*/\1/p')
if [ -n "$current_job" ]; then
fg %$current_job
# 如果用户在前台按Ctrl+Z,会返回这里
fi
;;
b)
# 将当前作业切换到后台
current_job=$(jobs | grep '+ ' | head -1 | sed -n 's/\[\([0-9]\+\)\]+.*/\1/p')
if [ -n "$current_job" ]; then
bg %$current_job 2>/dev/null
echo "作业 $current_job 已切换到后台"
fi
;;
esac
fi
}
# 主监控循环
main() {
echo "启动作业监控面板..."
echo "按 q 键退出,其他快捷键见面板说明"
sleep 2
while true; do
show_monitor
handle_input
sleep "$MONITOR_INTERVAL"
done
}
# 启动监控
main
#!/bin/bash
# job_cleaner.sh - 作业自动清理系统
CLEANUP_INTERVAL=60 # 检查间隔(秒)
MAX_RUNTIME=3600 # 最大运行时间(秒),1小时
LOG_FILE="/var/log/job_cleaner.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# 清理完成或异常的作业
cleanup_jobs() {
log "开始作业清理检查..."
# 获取所有作业
local jobs_info=$(jobs -l)
if [ -z "$jobs_info" ]; then
log "没有作业需要清理"
return 0
fi
# 解析每行作业信息
echo "$jobs_info" | 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_status=$(echo "$line" | awk '{print $3}')
local job_cmd=$(echo "$line" | cut -d' ' -f4-)
log "检查作业 $job_num (PID: $job_pid, 状态: $job_status)"
# 检查进程是否存在
if ! ps -p "$job_pid" > /dev/null 2>&1; then
log " 作业 $job_num 的进程 $job_pid 不存在,从作业表移除"
# 使用 disown 或等待作业自动移除
disown $job_pid 2>/dev/null || true
continue
fi
# 检查运行时间
local start_time=$(ps -o start= -p "$job_pid" 2>/dev/null)
if [ -n "$start_time" ]; then
# 计算运行时间(简化处理)
local current_time=$(date +%s)
local start_seconds=$(date -d "$start_time" +%s 2>/dev/null || echo $((current_time - 300)))
local runtime=$((current_time - start_seconds))
if [ $runtime -gt $MAX_RUNTIME ]; then
log " 作业 $job_num 已运行 ${runtime}秒,超过最大运行时间 ${MAX_RUNTIME}秒"
# 根据命令类型决定处理方式
case "$job_cmd" in
*make*|*compile*|*build*)
log " 编译任务,允许继续运行"
;;
*download*|*wget*|*curl*)
log " 下载任务,检查进度后决定"
# 可以添加下载进度检查逻辑
;;
*)
log " 发送TERM信号终止作业"
kill -TERM "$job_pid" 2>/dev/null
sleep 2
# 如果进程还在,强制杀死
if ps -p "$job_pid" > /dev/null 2>&1; then
log " 进程仍在运行,发送KILL信号"
kill -KILL "$job_pid" 2>/dev/null
fi
;;
esac
fi
fi
# 检查僵尸进程
if [ "$job_status" = "Done" ] || [ "$job_status" = "Exit" ]; then
log " 作业 $job_num 状态为 $job_status,进行清理"
# 等待作业完全退出
wait $job_pid 2>/dev/null
fi
done
log "作业清理检查完成"
}
# 监控循环
monitor_loop() {
log "作业自动清理系统启动"
log "检查间隔: ${CLEANUP_INTERVAL}秒"
log "最大运行时间: ${MAX_RUNTIME}秒"
while true; do
cleanup_jobs
sleep "$CLEANUP_INTERVAL"
done
}
# 启动监控(后台运行)
start_cleaner() {
monitor_loop &
CLEANER_PID=$!
log "清理进程PID: $CLEANER_PID"
echo $CLEANER_PID > /tmp/job_cleaner.pid
}
# 停止监控
stop_cleaner() {
if [ -f /tmp/job_cleaner.pid ]; then
local pid=$(cat /tmp/job_cleaner.pid)
kill $pid 2>/dev/null
rm -f /tmp/job_cleaner.pid
log "清理系统已停止"
else
log "清理系统未运行"
fi
}
# 查看状态
status_cleaner() {
if [ -f /tmp/job_cleaner.pid ]; then
local pid=$(cat /tmp/job_cleaner.pid)
if ps -p $pid > /dev/null; then
log "清理系统运行中 (PID: $pid)"
tail -20 "$LOG_FILE"
else
log "清理进程已停止"
fi
else
log "清理系统未运行"
fi
}
# 主程序
case "$1" in
start)
start_cleaner
;;
stop)
stop_cleaner
;;
status)
status_cleaner
;;
cleanup)
cleanup_jobs
;;
*)
echo "用法: $0 {start|stop|status|cleanup}"
exit 1
;;
esac
#!/bin/bash
# job_backup.sh - 作业备份和恢复系统
BACKUP_DIR="$HOME/.job_backups"
BACKUP_FILE="$BACKUP_DIR/jobs_$(date +%Y%m%d_%H%M%S).txt"
RESTORE_SCRIPT="$BACKUP_DIR/restore_$(date +%Y%m%d_%H%M%S).sh"
# 创建备份目录
mkdir -p "$BACKUP_DIR"
# 备份当前作业
backup_jobs() {
echo "正在备份当前作业状态..."
# 获取作业信息
local jobs_info=$(jobs -l)
if [ -z "$jobs_info" ]; then
echo "没有作业需要备份"
return 1
fi
# 保存原始作业信息
echo "$jobs_info" > "$BACKUP_FILE"
# 生成恢复脚本
cat > "$RESTORE_SCRIPT" << EOF
#!/bin/bash
# 作业恢复脚本
# 生成时间: $(date)
# 原始作业信息:
$(echo "$jobs_info" | sed 's/^/# /')
echo "正在恢复作业..."
EOF
# 解析每个作业,生成恢复命令
echo "$jobs_info" | 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_status=$(echo "$line" | awk '{print $3}')
local job_cmd=$(echo "$line" | cut -d' ' -f4-)
# 移除末尾的 & 符号(如果有)
local clean_cmd=$(echo "$job_cmd" | sed 's/ &$//')
# 根据状态生成不同的恢复命令
case "$job_status" in
Running)
if [[ "$job_cmd" == *" &" ]]; then
# 后台运行作业
echo "# 恢复作业 $job_num: $clean_cmd" >> "$RESTORE_SCRIPT"
echo "$clean_cmd &" >> "$RESTORE_SCRIPT"
echo "echo '启动后台作业: $clean_cmd'" >> "$RESTORE_SCRIPT"
else
# 应该不会出现这种情况,但处理一下
echo "# 注意: 作业 $job_num 显示为Running但没有 &" >> "$RESTORE_SCRIPT"
fi
;;
Stopped)
# 暂停的作业,先启动再暂停
echo "# 恢复暂停的作业 $job_num: $clean_cmd" >> "$RESTORE_SCRIPT"
echo "$clean_cmd &" >> "$RESTORE_SCRIPT"
echo "sleep 0.5" >> "$RESTORE_SCRIPT"
echo "jobs -l | grep '$clean_cmd' | head -1 | sed -n 's/\\[\\([0-9]\\+\\)\\].*/\\1/p' | xargs -I {} kill -TSTP %{}" >> "$RESTORE_SCRIPT"
echo "echo '恢复暂停作业: $clean_cmd'" >> "$RESTORE_SCRIPT"
;;
*)
echo "# 无法恢复状态为 $job_status 的作业: $clean_cmd" >> "$RESTORE_SCRIPT"
;;
esac
done
# 添加完成提示
cat >> "$RESTORE_SCRIPT" << EOF
echo "作业恢复完成"
echo "使用 'jobs -l' 查看当前作业状态"
EOF
# 设置执行权限
chmod +x "$RESTORE_SCRIPT"
echo "备份完成!"
echo "备份文件: $BACKUP_FILE"
echo "恢复脚本: $RESTORE_SCRIPT"
echo
echo "要恢复作业,请执行:"
echo " source $RESTORE_SCRIPT"
echo "或"
echo " bash $RESTORE_SCRIPT"
}
# 列出所有备份
list_backups() {
echo "可用备份列表:"
echo "序号 | 备份时间 | 备份文件"
echo "----------------------------"
local count=1
for file in "$BACKUP_DIR"/jobs_*.txt; do
if [ -f "$file" ]; then
local timestamp=$(basename "$file" | sed 's/jobs_//;s/\.txt//')
local display_time=$(echo "$timestamp" | sed 's/_/ /')
printf "%-4s | %-19s | %s\n" "$count" "$display_time" "$file"
count=$((count + 1))
fi
done
}
# 恢复指定备份
restore_backup() {
local backup_num="$1"
if [ -z "$backup_num" ]; then
echo "请指定备份编号"
list_backups
return 1
fi
# 查找对应的备份文件
local backup_files=("$BACKUP_DIR"/jobs_*.txt)
if [ ${#backup_files[@]} -eq 0 ]; then
echo "没有找到备份文件"
return 1
fi
if [ "$backup_num" -gt 0 ] && [ "$backup_num" -le ${#backup_files[@]} ]; then
local backup_file="${backup_files[$((backup_num-1))]}"
local restore_script="${backup_file%.txt}.sh"
if [ -f "$restore_script" ]; then
echo "从备份恢复作业: $backup_file"
echo "执行恢复脚本..."
echo "----------------------------------------"
bash "$restore_script"
else
echo "找不到对应的恢复脚本: $restore_script"
fi
else
echo "无效的备份编号: $backup_num"
list_backups
return 1
fi
}
# 清理旧备份
cleanup_backups() {
local keep_days=7 # 保留最近7天的备份
echo "清理超过 ${keep_days} 天的旧备份..."
find "$BACKUP_DIR" -name "jobs_*.txt" -mtime +$keep_days -exec rm -f {} \;
find "$BACKUP_DIR" -name "restore_*.sh" -mtime +$keep_days -exec rm -f {} \;
echo "备份清理完成"
}
# 主程序
case "$1" in
backup)
backup_jobs
;;
list)
list_backups
;;
restore)
shift
restore_backup "$@"
;;
cleanup)
cleanup_backups
;;
*)
echo "用法: $0 {backup|list|restore [编号]|cleanup}"
echo
echo "示例:"
echo " $0 backup # 备份当前作业"
echo " $0 list # 列出所有备份"
echo " $0 restore 1 # 恢复第1个备份"
echo " $0 cleanup # 清理旧备份"
exit 1
;;
esac
#!/bin/bash
# job_report.sh - 作业统计和报告系统
REPORT_DIR="$HOME/.job_reports"
REPORT_FILE="$REPORT_DIR/report_$(date +%Y%m%d_%H%M%S).html"
LOG_FILE="$HOME/.job_history.log"
# 初始化
init() {
mkdir -p "$REPORT_DIR"
# 记录作业变化到日志
trap 'log_job_change' DEBUG
}
# 记录作业变化
log_job_change() {
local current_jobs=$(jobs -l 2>/dev/null | wc -l)
local last_jobs=${LAST_JOB_COUNT:-0}
if [ "$current_jobs" -ne "$last_jobs" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 作业数量变化: $last_jobs -> $current_jobs" >> "$LOG_FILE"
jobs -l >> "$LOG_FILE"
echo "---" >> "$LOG_FILE"
fi
LAST_JOB_COUNT=$current_jobs
}
# 生成HTML报告
generate_html_report() {
local jobs_info=$(jobs -l)
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
cat > "$REPORT_FILE" << EOF
作业状态报告 - $timestamp
作业状态报告
统计信息
EOF
# 计算统计信息
local total_jobs=$(jobs | wc -l)
local running_jobs=$(jobs -r | wc -l)
local stopped_jobs=$(jobs -s | wc -l)
local system_info=$(uname -a)
local user_info=$(whoami)@$(hostname)
cat >> "$REPORT_FILE" << EOF
系统: $system_info
用户: $user_info
终端: $(tty)
总作业数: $total_jobs
运行中: $running_jobs
暂停中: $stopped_jobs
作业详情
EOF
if [ -z "$jobs_info" ]; then
echo "当前没有活动的作业。
" >> "$REPORT_FILE"
else
echo "" >> "$REPORT_FILE"
echo "作业号 PID 状态 命令 启动时间 CPU% 内存% " >> "$REPORT_FILE"
# 解析每个作业
echo "$jobs_info" | 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_status=$(echo "$line" | awk '{print $3}')
local job_cmd=$(echo "$line" | cut -d' ' -f4-)
# 获取进程详细信息
local proc_info=$(ps -o start_time,pcpu,pmem,cmd -p "$job_pid" 2>/dev/null | tail -1)
local start_time=$(echo "$proc_info" | awk '{print $1}')
local cpu_usage=$(echo "$proc_info" | awk '{print $2}')
local mem_usage=$(echo "$proc_info" | awk '{print $3}')
# 确定状态类别
local status_class
case "$job_status" in
Running) status_class="running" ;;
Stopped) status_class="stopped" ;;
Done) status_class="done" ;;
Killed|Terminated|Exit*) status_class="killed" ;;
*) status_class="" ;;
esac
cat >> "$REPORT_FILE" << EOF
$job_num
$job_pid
$job_status
$job_cmd
$start_time
$cpu_usage%
$mem_usage%
EOF
done
echo "
" >> "$REPORT_FILE"
fi
# 添加历史记录部分
if [ -f "$LOG_FILE" ]; then
local recent_changes=$(tail -10 "$LOG_FILE" | sed 's/&/\&/g; s/\</g; s/>/\>/g')
cat >> "$REPORT_FILE" << EOF
最近变化记录
$recent_changes
EOF
fi
# 添加资源使用图表(简单文本图表)
cat >> "$REPORT_FILE" << EOF
资源使用情况
$(top -bn1 | head -20 | sed 's/&/\&/g; s/\</g; s/>/\>/g')
EOF
echo "报告已生成: $REPORT_FILE"
echo "在浏览器中打开: file://$REPORT_FILE"
}
# 生成文本报告
generate_text_report() {
echo "=== 作业状态报告 ==="
echo "生成时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "系统: $(uname -a)"
echo "用户: $(whoami)@$(hostname)"
echo "终端: $(tty)"
echo
echo "=== 统计信息 ==="
echo "总作业数: $(jobs | wc -l)"
echo "运行中: $(jobs -r | wc -l)"
echo "暂停中: $(jobs -s | wc -l)"
echo
echo "=== 作业详情 ==="
jobs -l
echo
echo "=== 进程详情 ==="
jobs -p | while read pid; do
echo "进程 $pid:"
ps -o pid,ppid,user,start_time,pcpu,pmem,cmd -p "$pid" 2>/dev/null
echo
done
echo "=== 系统资源 ==="
top -bn1 | head -10
}
# 邮件报告
mail_report() {
local recipient="$1"
if [ -z "$recipient" ]; then
echo "请指定收件人邮箱"
return 1
fi
generate_text_report | mail -s "作业状态报告 $(date '+%Y-%m-%d %H:%M:%S')" "$recipient"
echo "报告已发送到: $recipient"
}
# 主程序
case "$1" in
html)
generate_html_report
;;
text)
generate_text_report
;;
mail)
shift
mail_report "$@"
;;
monitor)
echo "开始监控模式,每30秒更新一次报告..."
while true; do
clear
generate_text_report
sleep 30
done
;;
*)
echo "用法: $0 {html|text|mail 邮箱|monitor}"
echo
echo "示例:"
echo " $0 html # 生成HTML报告"
echo " $0 text # 生成文本报告"
echo " $0 mail user@example.com # 邮件发送报告"
echo " $0 monitor # 监控模式(实时更新)"
exit 1
;;
esac
| 组合命令 | 功能 | 示例 |
|---|---|---|
jobs && fg |
查看作业后切换到当前作业 | jobs && fg |
jobs -p | xargs kill |
终止所有作业 | jobs -p | xargs kill -9 |
jobs -r && fg %1 |
有运行中作业时切换到作业1 | if jobs -r > /dev/null; then fg %1; fi |
jobs -s && bg |
有暂停作业时放入后台继续 | jobs -s && bg %% |
jobs -l | grep -i "python" |
查找包含特定字符串的作业 | jobs -l | grep -i "error\|fail" |
watch -n 1 jobs -l |
每秒刷新作业状态 | watch -n 0.5 'jobs -l; echo; ps aux | head -10' |
set -m启用作业控制+表示当前作业(最近使用的作业),-表示上一个作业jobs命令没有输出通常表示当前Shell会话中没有作业。可能的原因和解决方法:
# 1. 确实没有作业
# 如果从未启动过后台作业或暂停过前台作业,jobs自然没有输出
# 2. 在不同的Shell会话中
# 每个Shell会话有自己的作业表
# 在终端A中启动的作业,在终端B中无法看到
# 3. 作业已经完成
# 完成的作业会自动从作业表中移除
sleep 5 &
# 等待5秒后
jobs # 可能没有输出,因为sleep已经完成
# 4. 使用了disown命令
# disown会将作业从作业表中移除
sleep 1000 &
disown
jobs # 没有输出
# 5. 脚本中默认禁用作业控制
# 在脚本中,需要显式启用作业控制
#!/bin/bash
set -m # 启用作业控制
sleep 1000 &
jobs # 现在应该有输出
# 6. Shell配置问题
# 检查当前Shell是否支持作业控制
echo $-
# 如果输出包含"m",则表示支持作业控制
# 7. 作业被终止
# 如果作业被kill命令终止,会从作业表中移除
# 8. 检查是否在子Shell中
# 在子Shell中执行的作业属于子Shell
( sleep 1000 & )
jobs # 没有输出,因为作业在子Shell中
# 9. 快速完成的任务
# 如果任务执行非常快,可能来不及看到
true &
jobs # 可能看不到,因为true命令立即完成
# 10. 使用jobs -l查看更详细信息
# 有时候默认输出可能不显示某些作业
jobs -l
如果jobs显示的状态与实际不符,可以尝试以下方法:
# 1. 刷新作业状态
# 有时状态更新有延迟,可以强制刷新
jobs
# 2. 使用ps命令验证进程状态
# 获取所有作业的PID
jobs -p | while read pid; do
echo "PID $pid 状态:"
ps -o pid,state,cmd -p "$pid"
done
# 3. 检查进程是否真的存在
# 有时候进程已经终止,但作业表还未更新
validate_jobs() {
jobs -l | while read line; do
job_num=$(echo "$line" | sed -n 's/\[\([0-9]\+\)\].*/\1/p')
job_pid=$(echo "$line" | awk '{print $2}')
if ! ps -p "$job_pid" > /dev/null 2>&1; then
echo "作业 $job_num (PID: $job_pid) 的进程不存在"
# 尝试从作业表移除
disown "$job_pid" 2>/dev/null
fi
done
}
# 4. 检查僵尸进程
# 如果进程已经变成僵尸(Z状态),jobs可能仍然显示Running
jobs -l | awk '$3=="Running" {print $2}' | while read pid; do
state=$(ps -o state= -p "$pid" 2>/dev/null)
if [ "$state" = "Z" ]; then
echo "警告: 进程 $pid 是僵尸进程"
fi
done
# 5. 手动同步作业状态
# 尝试向作业发送空信号(0),这会检查进程是否存在
sync_job_status() {
for job in $(jobs | awk '{print $1}' | tr -d '[]+'); do
if kill -0 %$job 2>/dev/null; then
echo "作业 $job 存在"
else
echo "作业 $job 不存在,状态可能过时"
fi
done
}
# 6. 检查信号处理
# 有些程序可能捕获了SIGTSTP等信号
# 可以尝试发送不同的信号
test_job_signals() {
job_pid=$(jobs -l | awk 'NR==1{print $2}')
echo "测试进程 $job_pid 的信号处理:"
# 测试SIGTSTP(Ctrl+Z)
if kill -TSTP "$job_pid" 2>/dev/null; then
echo " 进程响应 SIGTSTP"
else
echo " 进程忽略 SIGTSTP"
fi
# 测试SIGCONT(继续)
if kill -CONT "$job_pid" 2>/dev/null; then
echo " 进程响应 SIGCONT"
fi
}
# 7. 重启Shell会话
# 如果作业表严重不一致,最简单的方法是重启Shell
# 注意:这会终止所有作业
echo "如果问题持续存在,考虑重启Shell会话"
echo "警告:重启会终止所有未受保护的作业"
# 8. 使用外部进程管理工具
# 对于重要任务,使用更可靠的进程管理工具
# 如:systemd, supervisord, pm2等
echo "对于生产环境,建议使用专业进程管理工具"
默认情况下,不同终端的作业信息是独立的。但可以通过以下方法实现共享:
# 方法1: 使用screen或tmux(终端复用器)
# 这些工具允许在多个"窗口"中共享同一个Shell会话
# 使用screen
screen -S mysession
# 启动作业
sleep 1000 &
# 分离会话: Ctrl+A D
# 在另一个终端重新连接
screen -r mysession
# jobs会显示相同的作业
# 方法2: 使用命名管道共享作业信息
# 终端A:导出作业信息
export_jobs() {
while true; do
jobs -l > /tmp/shared_jobs.txt
sleep 2
done
}
export_jobs &
# 终端B:读取作业信息
watch -n 2 'cat /tmp/shared_jobs.txt'
# 方法3: 使用网络套接字
# 更复杂的方案,可以实现跨机器共享
# 方法4: 使用共享内存
# 通过/dev/shm共享作业状态
share_jobs() {
while true; do
jobs -l > /dev/shm/jobs_status
sleep 1
done
}
# 其他终端查看
cat /dev/shm/jobs_status
# 方法5: 使用数据库
# 将作业状态写入数据库,所有终端从数据库读取
# 方法6: 使用消息队列
# 通过消息队列同步作业状态
# 方法7: 自定义作业管理器
# 实现一个集中式的作业管理服务
# 简单的作业共享脚本示例
#!/bin/bash
# job_server.sh - 简单的作业状态服务器
PORT=9999
STATUS_FILE="/tmp/jobs_status.$$"
# 清理函数
cleanup() {
rm -f "$STATUS_FILE"
kill $SERVER_PID 2>/dev/null
exit
}
trap cleanup INT TERM EXIT
# 启动状态更新服务
(
while true; do
jobs -l > "$STATUS_FILE"
sleep 1
done
) &
UPDATE_PID=$!
# 启动HTTP服务器提供状态
python3 -m http.server --directory /tmp --bind 0.0.0.0 $PORT &
SERVER_PID=$!
echo "作业状态服务器已启动"
echo "访问 http://localhost:$PORT/$(basename $STATUS_FILE) 查看状态"
echo "按Ctrl+C停止服务器"
wait
# 方法8: 使用SSH远程执行
# 在一个终端中控制另一个终端的作业
# 需要SSH密钥认证
ssh user@localhost 'jobs -l'
# 方法9: 使用终端组
# Linux支持终端组,但配置较复杂
# 可以使用脚本模拟
# 方法10: 最好的实践 - 使用进程管理器
# 对于需要跨终端管理的作业,使用专业工具
# 如:systemd用户服务
systemctl --user start myjob.service
# 在任何终端都可以查看状态
systemctl --user status myjob.service
推荐做法:
screen或tmuxsystemd或其他进程管理器在生产环境中使用jobs命令需要注意以下事项:
# 1. 避免依赖Shell作业控制
# 生产环境应该使用专业的进程管理工具
# 不推荐:依赖jobs/fg/bg管理重要服务
# 推荐:使用systemd, supervisord, docker等
# 2. 监控脚本中的使用
# 在监控脚本中,jobs可以用于检查进程状态
check_service() {
# 检查服务是否在后台运行
if jobs -l | grep -q "my_service"; then
echo "服务正在运行"
return 0
else
echo "服务未运行"
return 1
fi
}
# 3. 结合nohup使用
# 对于需要长期运行的任务
start_background_task() {
nohup ./long_running_task.sh > task.log 2>&1 &
TASK_PID=$!
echo "任务已启动,PID: $TASK_PID"
echo "日志文件: task.log"
# 可以将PID保存到文件,便于管理
echo $TASK_PID > /var/run/task.pid
}
# 4. 使用系统服务(推荐)
# 创建systemd服务文件
cat > /etc/systemd/system/myapp.service << EOF
[Unit]
Description=My Application
After=network.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/start.sh
Restart=always
RestartSec=10
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=myapp
[Install]
WantedBy=multi-user.target
EOF
# 管理服务
sudo systemctl start myapp
sudo systemctl status myapp
sudo systemctl stop myapp
# 5. 在容器环境中
# 使用Docker等容器技术
docker run -d --name myapp myapp:latest
docker ps # 查看运行中的容器
docker logs myapp # 查看日志
# 6. 使用进程管理工具
# 如:supervisord
# 配置文件: /etc/supervisor/conf.d/myapp.conf
[program:myapp]
command=/opt/myapp/start.sh
autostart=true
autorestart=true
stderr_logfile=/var/log/myapp.err.log
stdout_logfile=/var/log/myapp.out.log
# 管理
supervisorctl start myapp
supervisorctl status myapp
supervisorctl stop myapp
# 7. 如果需要使用jobs,确保会话持久化
# 使用screen或tmux保持会话
# 启动tmux会话
tmux new-session -d -s production './start_production.sh'
# 查看会话
tmux list-sessions
# 连接到会话
tmux attach-session -t production
# 8. 作业的自动恢复
# 使用脚本监控和恢复作业
monitor_and_restart() {
while true; do
if ! jobs -l | grep -q "critical_task"; then
echo "[$(date)] 关键任务停止,重新启动..."
./critical_task.sh &
fi
sleep 60
done
}
# 9. 日志和审计
# 记录所有作业操作
log_job_operation() {
echo "[$(date)] $USER 执行: $@" >> /var/log/job_audit.log
jobs -l >> /var/log/job_audit.log
}
# 包装jobs命令进行审计
jobs() {
log_job_operation "jobs $@"
command jobs "$@"
}
# 10. 权限和安全性
# 生产环境中要注意权限控制
# 使用最小权限原则运行作业
sudo -u appuser jobs # 以特定用户身份运行
# 11. 资源限制
# 为作业设置资源限制
ulimit -c unlimited # 允许core dump
ulimit -n 65536 # 文件描述符限制
ulimit -u unlimited # 用户进程数限制
# 12. 信号处理
# 确保作业正确处理信号
trap 'cleanup_and_exit' INT TERM EXIT
# 13. 健康检查
# 定期检查作业健康状态
health_check() {
for pid in $(jobs -p); do
# 检查进程是否响应
if ! kill -0 $pid 2>/dev/null; then
echo "进程 $pid 无响应"
# 重启逻辑
fi
# 检查资源使用
local cpu_usage=$(ps -o pcpu= -p $pid)
if [ $(echo "$cpu_usage > 90" | bc) -eq 1 ]; then
echo "进程 $pid CPU使用过高: ${cpu_usage}%"
fi
done
}
# 14. 告警机制
# 作业异常时发送告警
send_alert() {
local message="$1"
# 发送邮件
echo "$message" | mail -s "作业告警" admin@example.com
# 或发送到监控系统
curl -X POST https://monitor.example.com/alerts \
-d "message=$message"
}
生产环境最佳实践:
jobs -p | xargs与其他命令配合