🖥️ Day 6 of 7

进程管理与系统编程

📖 预计时长:2.5 小时 🎯 难度:中高级
学会编写健壮的生产级脚本:信号处理、后台进程、定时任务、日志规范、错误处理。这些是脚本从"能用"到"好用"的关键跃升。
进程管理命令 trap 信号捕获 后台进程与 nohup crontab 定时任务 规范日志系统 健壮性错误处理

1️⃣ 进程管理命令

# 查看进程
ps aux                      # 所有进程详细列表
ps -ef | grep nginx          # 查找 nginx 进程
pgrep nginx                  # 按名称查 PID
pgrep -a nginx               # 显示完整命令
pidof sshd                   # 查找进程 PID
top                          # 动态进程监控
htop                         # 更好用的 top(需安装)

# 发送信号
kill 12345                   # 发送 SIGTERM(正常终止)
kill -9 12345                # 发送 SIGKILL(强制杀死)
kill -HUP 12345              # 发送 SIGHUP(重新加载)
killall nginx                # 按名称杀进程
pkill -f "python app.py"     # 按命令行模式杀
bash

常用信号表

信号名编号含义
SIGHUP1挂起/重读配置,重启不终止
SIGINT2中断(Ctrl+C)
SIGQUIT3退出并产生 core dump(Ctrl+\)
SIGKILL9强制终止,不可被捕获
SIGTERM15正常终止(默认)
SIGSTOP17/19暂停进程,不可被捕获
SIGCONT18/20恢复暂停的进程
SIGUSR110用户自定义信号1
EXIT0脚本退出时(trap 特用)

2️⃣ trap 信号捕获

trap 让脚本能够捕获信号,在中断/退出时执行清理工作。

#!/bin/bash

# 清理函数
cleanup() {
    echo "\n[清理] 正在清理临时文件..."
    rm -f /tmp/myscript_*
    echo "[清理] 完成,脚本退出"
}

# 注册信号处理
trap cleanup EXIT     # 脚本任何方式退出时都执行
trap cleanup INT TERM  # Ctrl+C 或 kill 时执行

# 创建临时文件
TMP_FILE=$(mktemp /tmp/myscript_XXXXXX)
echo "临时文件:$TMP_FILE"

# 主要工作
echo "脚本执行中,按 Ctrl+C 可测试清理..."
sleep 30

# 忽略信号
trap "" HUP     # 忽略 SIGHUP

# 恢复默认处理
trap - INT      # 恢复 INT 的默认处理
bash

3️⃣ 后台进程管理

# & 放到后台执行
long_task.sh &
echo "后台PID: $!"

# jobs 查看后台任务
jobs
jobs -l  # 显示 PID

# fg/bg 前后台切换
fg %1    # 将任务1调到前台
bg %1    # 将任务1放到后台继续

# wait:等待后台任务完成
task1.sh & pid1=$!
task2.sh & pid2=$!
wait $pid1; echo "task1 完成"
wait $pid2; echo "task2 完成"
wait     # 等待所有后台任务
bash
# nohup:关闭终端后继续运行
nohup ./long_script.sh > /var/log/myscript.log 2>&1 &
echo "PID: $!"

# screen / tmux:会话持久化(更推荐)
screen -S mysession  # 新建命名会话
screen -ls           # 列出所有会话
screen -r mysession  # 恢复会话

# 控制并发数(同时最多5个并行任务)
max_jobs=5
for item in ${list[@]}; do
    while [[ $(jobs -r | wc -l) -ge $max_jobs ]]; do
        sleep 0.5
    done
    process_item "$item" &
done
wait
bash

4️⃣ crontab 定时任务

# crontab 时间格式
# 分(0-59) 时(0-23) 日(1-31) 月(1-12) 周(0-7,0和7均为周日)
# *=任意 ,=列举 -=范围 /=步长

# 常用示例
# 每天凌晨2点执行备份
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

# 每5分钟检查一次
*/5 * * * * /opt/scripts/monitor.sh

# 每周一上午9点
0 9 * * 1 /opt/scripts/weekly_report.sh

# 每月1日0点
0 0 1 * * /opt/scripts/monthly_cleanup.sh

# 每天8:30和17:30
30 8,17 * * * /opt/scripts/notify.sh

# 工作日(周一至周五)每小时执行
0 * * * 1-5 /opt/scripts/workday_check.sh

# 系统重启时执行
@reboot /opt/scripts/startup.sh
cron
# crontab 管理命令
crontab -l          # 列出当前用户的定时任务
crontab -e          # 编辑定时任务
crontab -r          # 删除所有定时任务(危险!)
crontab -u root -l  # 查看 root 的定时任务

# 系统级 crontab(/etc/crontab 多一个用户字段)
# 分 时 日 月 周  用户名  命令
# 0  2  *  *  *   root   /opt/scripts/backup.sh

# cron 日志
grep CRON /var/log/syslog | tail -20
bash
⚠️
cron 脚本注意事项cron 的 PATH 很短,脚本中务必使用绝对路径;cron 没有交互终端,不要用 read;务必将输出重定向到日志文件,否则会发邮件或丢失输出。

5️⃣ 规范的日志系统

#!/bin/bash
# 生产级日志函数库

# 颜色常量(终端)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'  # 重置颜色

# 日志文件路径
LOG_FILE="/var/log/myscript/$(date '+%Y%m%d').log"
mkdir -p "$(dirname "$LOG_FILE")"

# 日志函数
log() {
    local level="$1"
    shift
    local msg="$*"
    local ts=$(date '+%Y-%m-%d %H:%M:%S')
    local color=$NC

    case "$level" in
        INFO)    color=$GREEN  ;;
        WARN)    color=$YELLOW ;;
        ERROR)   color=$RED    ;;
        DEBUG)   color=$BLUE   ;;
    esac

    # 写入文件(无颜色)
    echo "[$ts] [$level] $msg" >> "$LOG_FILE"
    # 输出到终端(有颜色)
    printf "${color}[%s] [%s]${NC} %s\n" "$ts" "$level" "$msg"
}

log_info()  { log INFO  "$@"; }
log_warn()  { log WARN  "$@"; }
log_error() { log ERROR "$@"; }
log_debug() { [[ "${DEBUG:-0}" == "1" ]] && log DEBUG "$@"; }

# 使用示例
log_info  "脚本启动"
log_warn  "磁盘使用率超过80%"
log_error "备份失败:连接超时"
bash

6️⃣ 健壮性错误处理

#!/bin/bash
# 生产脚本模板

set -euo pipefail  # 严格模式

# 错误处理函数
error_handler() {
    local exit_code=$?
    local line_no=$1
    log_error "脚本在第 ${line_no} 行执行失败,退出码: ${exit_code}"
    exit $exit_code
}
trap 'error_handler $LINENO' ERR

# 安全执行命令(失败不退出,返回状态码)
run_safe() {
    if ! "$@"; then
        log_warn "命令失败(忽略): $*"
        return 1
    fi
}

# 检查依赖命令
check_deps() {
    local missing=()
    for cmd in "$@"; do
        command -v "$cmd" >/dev/null 2>&1 || missing+=("$cmd")
    done
    if [[ ${#missing[@]} -gt 0 ]]; then
        log_error "缺少依赖命令:${missing[*]}"
        exit 127
    fi
}

# 使用文件锁防止重复运行
LOCK_FILE="/tmp/$(basename "$0").lock"
if ! mkdir "$LOCK_FILE" 2>/dev/null; then
    log_error "脚本已在运行,退出"
    exit 1
fi
trap "rm -rf '$LOCK_FILE'" EXIT

# 检查依赖
check_deps curl jq rsync

# 脚本主体...
bash

🏋️ 今日练习

  1. 写一个后台监控脚本,每分钟检查 nginx 进程,若停止则自动重启,并记录日志
  2. 为你的脚本添加完整的 trap 处理:中断时清理临时文件,错误时记录行号,退出时打印耗时
  3. 设置5个 crontab 任务:每天备份、每小时清理日志、每周发报告、每天检查磁盘、开机启动检查
  4. 写一个使用文件锁的脚本,保证同时只有一个实例运行
  5. 编写一个并行任务脚本,同时向10个服务器检查连通性,等所有检查完成后输出汇总报告