Linux Shell 脚本控制全指南:从信号捕获到定时调度
概述
在 Linux 环境中,运行 Shell 脚本并非只有 “直接执行” 这一种方式。实际运维或开发场景中,我们常需要:
- 防止脚本被误中断(如 Ctrl+C 误触);
- 让脚本在后台静默运行,不阻塞终端;
- 退出终端后仍保持脚本运行;
- 按指定时间 / 频率自动执行脚本(如定时备份、日志清理)。
这些需求的本质是对脚本的 “生命周期” 和 “运行环境” 进行精准控制。本文将从 “信号处理”“后台运行”“作业控制”“定时调度” 四大维度,系统讲解 Shell 脚本的全场景控制技巧,帮你解决脚本运行中的各类 “失控” 问题。
一、基础:Linux 信号与脚本捕获
Linux 系统通过信号(Signal) 与进程通信,实现 “中断”“暂停”“终止” 等控制。Shell 脚本默认会继承系统对信号的处理逻辑,但我们可以通过 trap 命令自定义信号响应,让脚本更 “抗干扰”。
1. 必知的 Linux 核心信号
Linux 系统有 30+ 种信号,下表列出脚本控制中最常用的 8 种,需重点记忆:
信号值 | 信号名 | 含义说明 | 常见触发方式 | 默认行为(脚本) |
---|---|---|---|---|
1 | SIGHUP | 挂起信号(终端断开时发送) | 退出 SSH 终端、关闭终端 | 终止脚本 |
2 | SIGINT | 中断信号(用户请求中断) | 按下 Ctrl+C | 终止脚本 |
3 | SIGQUIT | 退出信号(用户请求退出并生成核心 dump) | 按下 Ctrl+\ | 终止脚本并生成 dump 文件 |
9 | SIGKILL | 强制终止信号(无条件) | kill -9 PID | 立即终止(无法捕获) |
15 | SIGTERM | 温和终止信号(允许进程清理资源) | kill PID(默认信号) | 终止脚本(可捕获) |
18 | SIGCONT | 继续信号(恢复被暂停的进程) | bg/fg 命令 | 恢复脚本运行 |
19 | SIGSTOP | 强制暂停信号(无条件) | kill -19 PID | 暂停脚本(无法捕获) |
20 | SIGTSTP | 终端暂停信号(用户请求暂停) | 按下 Ctrl+Z | 暂停脚本(可捕获) |
关键特性:
- SIGKILL(9) 和 SIGSTOP(19) 是 “强制信号”,脚本无法通过 trap 捕获,只能被动响应;
- 其他信号(如 SIGINT(2)、SIGTERM(15))可通过 trap 自定义处理逻辑(如提示用户、清理临时文件)。
2. 用键盘生成信号:应急控制脚本
日常操作中,我们常用键盘组合键生成信号,快速控制失控脚本:
场景 1:中断失控脚本(Ctrl+C → SIGINT)
当脚本陷入死循环或执行过久时,按下 Ctrl+C 发送 SIGINT 信号,强制中断脚本:
# 示例:故意写一个死循环脚本
while true; do
echo "运行中...(按 Ctrl+C 中断)"
sleep 1
done
执行后按下 Ctrl+C,脚本会立即终止,终端显示:
运行中...(按 Ctrl+C 中断)
运行中...(按 Ctrl+C 中断)
^C # 按下 Ctrl+C 后的反馈
####场景 2:暂停脚本(Ctrl+Z → SIGTSTP)
若不想终止脚本(如需要保留中间状态),可按下 Ctrl+Z 发送 SIGTSTP 信号,暂停脚本:
# 执行上述死循环脚本,按下 Ctrl+Z
^Z
[1]+ Stopped ./loop.sh # 脚本被暂停,分配作业号 1
暂停后的脚本仍驻留内存,可通过后续命令恢复运行(见 “作业控制” 章节)。
3. 用 trap 命令捕获信号:自定义响应
trap 命令允许脚本 “拦截” 指定信号,并执行自定义操作(如提示、清理、忽略),格式如下:
# 格式 1:捕获信号后执行命令(多个信号用空格分隔)
trap "命令1; 命令2" 信号1 信号2
# 格式 2:忽略信号(命令部分为空)
trap "" 信号1 信号2
# 格式 3:恢复信号默认行为(命令部分为 --)
trap -- 信号1 信号2
示例 1:捕获 SIGINT,防止误中断
脚本执行关键操作(如文件拷贝、数据库写入)时,防止用户误按 Ctrl+C 中断:
#!/bin/bash
# 脚本名:safe_copy.sh
# 功能:捕获 Ctrl+C,提示用户确认后再中断
# 1. 定义信号捕获逻辑:收到 SIGINT 时提示
trap "echo -e '\n⚠️ 请勿误按 Ctrl+C!若需终止,请输入 exit 或 kill 脚本PID'" SIGINT
# 2. 模拟关键操作(如拷贝大文件)
echo "开始拷贝大文件(预计 10 秒)..."
for ((i=1; i<=10; i++)); do
echo "拷贝进度:$i/10"
sleep 1
done
echo "✅ 拷贝完成!"
执行脚本并按 Ctrl+C,效果如下:
./safe_copy.sh
开始拷贝大文件(预计 10 秒)...
拷贝进度:1/10
拷贝进度:2/10
^C # 按下 Ctrl+C
⚠️ 请勿误按 Ctrl+C!若需终止,请输入 exit 或 kill 脚本PID
拷贝进度:3/10 # 脚本继续运行,未被中断
示例 2:捕获 EXIT,清理临时文件
脚本退出时(无论正常结束还是被信号终止),自动清理临时文件,避免残留:
#!/bin/bash
# 脚本名:clean_temp.sh
# 功能:退出时自动删除临时文件
# 1. 创建临时文件
temp_file=$(mktemp /tmp/my_temp.XXXXXX)
echo "临时文件路径:$temp_file"
# 2. 捕获 EXIT 信号(脚本退出时触发),删除临时文件
trap "echo '🗑️ 清理临时文件...'; rm -f $temp_file" EXIT
# 3. 模拟业务逻辑
echo "正在处理数据..."
sleep 5
echo "✅ 数据处理完成,脚本即将退出"
执行脚本后,无论正常结束还是按 Ctrl+C 中断,临时文件都会被清理:
./clean_temp.sh
临时文件路径:/tmp/my_temp.3z8xQ7
正在处理数据...
^C # 按下 Ctrl+C 中断
🗑️ 清理临时文件... # 触发 trap 中的清理命令
# 验证临时文件是否存在(已被删除)
ls /tmp/my_temp.3z8xQ7 # 提示:No such file or directory
示例 3:动态修改 / 移除信号捕获
脚本可在不同阶段修改信号处理逻辑,例如 “关键操作时捕获信号,普通操作时恢复默认”:
#!/bin/bash
# 脚本名:dynamic_trap.sh
# 阶段 1:关键操作,捕获 SIGINT
echo "=== 关键操作阶段(禁止 Ctrl+C)==="
trap "echo -e '\n❌ 关键操作中,禁止中断!'" SIGINT
sleep 5 # 模拟关键操作
# 阶段 2:普通操作,恢复 SIGINT 默认行为
echo -e "\n=== 普通操作阶段(允许 Ctrl+C)==="
trap -- SIGINT # 恢复默认行为
sleep 5 # 此阶段按 Ctrl+C 可中断脚本
二、进阶:脚本的后台运行与脱离终端
直接在终端执行脚本会 “阻塞” 终端(需等待脚本结束才能输入新命令),且关闭终端后脚本会被终止。本节讲解如何让脚本 “后台运行” 并 “脱离终端控制”。
1. 用 & 后台运行脚本:基础用法
在脚本名后加 &,可将脚本放入后台运行,终端立即恢复可用:
# 脚本名:bg_demo.sh,模拟耗时操作
#!/bin/bash
for ((i=1; i<=5; i++)); do
echo "后台运行中:$i/5"
sleep 1
done
echo "✅ 后台脚本执行完成!"
执行方式与效果
# 后台运行脚本,终端立即返回提示符
./bg_demo.sh &
[1] 12345 # [作业号] PID(作业号用于后续控制)
# 终端可正常输入其他命令(如 pwd),脚本在后台继续运行
pwd
/home/user/scripts
# 脚本执行完成后,终端会显示结果(可能与其他命令输出混在一起)
后台运行中:1/5
后台运行中:2/5
后台运行中:3/5
后台运行中:4/5
后台运行中:5/5
✅ 后台脚本执行完成!
[1]+ Done ./bg_demo.sh # 作业状态提示
注意:重定向输出,避免混乱
后台脚本的 STDOUT 和 STDERR 仍默认指向终端,会导致脚本输出与终端命令混在一起。建议将输出重定向到文件:
# 后台运行,输出写入 log 文件,错误也写入同一文件
./bg_demo.sh > bg_log.txt 2>&1 &
[1] 12346
# 查看脚本运行进度(实时)
tail -f bg_log.txt
后台运行中:1/5
后台运行中:2/5
2. 用 nohup 脱离终端:退出后仍运行
& 后台运行的脚本依赖当前终端,关闭终端(如退出 SSH)会发送 SIGHUP 信号终止脚本。nohup 命令可 “阻断 SIGHUP 信号”,让脚本脱离终端控制,即使退出终端仍继续运行。
基本用法
# 格式:nohup 脚本命令 &
nohup ./bg_demo.sh > nohup_log.txt 2>&1 &
[1] 12347
nohup: ignoring input and appending output to 'nohup.out' # 提示:输出默认写入 nohup.out
关键特性
- 输出默认路径:若未指定输出文件,nohup 会自动将 STDOUT/STDERR 写入当前目录的 nohup.out(若无权限则写入 $HOME/nohup.out);
- 脱离终端:关闭终端后,脚本仍在后台运行,可通过 ps 命令查看:
# 查看脚本进程(bg_demo.sh)
ps aux | grep bg_demo.sh
user 12347 0.0 0.0 12348 2340 ? S 14:30 0:00 /bin/bash ./bg_demo.sh
- 终止方式:只能通过 kill 命令终止(kill PID 或 kill -9 PID):
kill 12347 # 温和终止
# 若无法终止,用强制终止
kill -9 12347
三、实战:作业控制(启动 / 暂停 / 恢复脚本)
当终端中有多个后台 / 暂停的脚本时,需要通过 “作业控制” 命令管理它们。核心命令包括 jobs(查看作业)、bg(后台恢复)、fg(前台恢复)。
1. 用 jobs 命令查看作业
jobs 命令列出当前终端的所有作业(包括后台运行、暂停的脚本),常用选项如下:
选项 | 功能说明 |
---|---|
-l | 显示作业号、PID 及状态(最常用) |
-n | 只显示上次查看后状态变化的作业 |
-r | 只显示正在运行的作业 |
-s | 只显示已暂停的作业 |
示例:查看作业状态
# 1. 启动 2 个脚本,1 个后台运行,1 个暂停
./bg_demo.sh > log1.txt 2>&1 & # 后台运行,作业号 1
./bg_demo.sh # 前台运行,按 Ctrl+Z 暂停,作业号 2
^Z
[2]+ Stopped ./bg_demo.sh
# 2. 查看所有作业(含 PID)
jobs -l
[1]- 12347 Running ./bg_demo.sh > log1.txt 2>&1 &
[2]+ 12348 Stopped ./bg_demo.sh
输出解读:
- [1]-/[2]+:+ 表示 “默认作业”(未指定作业号时,命令默认操作此作业),- 表示 “次默认作业”;
- Running/Stopped:作业状态;
- 12347/12348:作业对应的 PID。
2. 用 bg/fg 恢复暂停的作业
暂停的作业(Stopped 状态)可通过 bg 恢复到后台运行,或通过 fg 恢复到前台运行。
示例 1:后台恢复(bg)
# 恢复默认作业(作业 2,带 + 号)到后台
bg
[2]+ ./bg_demo.sh & # 作业 2 转为后台运行
# 查看更新后的作业状态
jobs -l
[1]- 12347 Running ./bg_demo.sh > log1.txt 2>&1 &
[2]+ 12348 Running ./bg_demo.sh &
示例 2:前台恢复(fg)
若需要交互(如脚本需要输入),可将作业恢复到前台(会阻塞终端,直到作业结束):
# 恢复作业 2 到前台(需指定作业号)
fg 2
./bg_demo.sh # 作业 2 转为前台运行,终端被阻塞
后台运行中:1/5
后台运行中:2/5
# 按 Ctrl+C 可中断,或等待执行完成
3. 终止作业:kill 命令
对暂停或后台运行的作业,可通过 kill 命令发送信号终止:
# 1. 查看作业 PID(通过 jobs -l)
jobs -l
[2]+ 12348 Running ./bg_demo.sh &
# 2. 发送 SIGTERM(默认)温和终止
kill 12348
[2]+ Terminated ./bg_demo.sh
# 3. 若无法终止,发送 SIGKILL 强制终止
kill -9 12348
[2]+ Killed ./bg_demo.sh
四、高级:调整脚本优先级(nice/renice)
Linux 是多任务系统,内核通过 “调度优先级(谦让度,Nice Value)” 分配 CPU 时间。默认情况下,脚本优先级为 0(取值范围:-20 最高 → 19 最低)。通过 nice 和 renice 命令,可调整脚本优先级,避免高耗 CPU 脚本影响系统核心服务。
1. 用 nice 启动低优先级脚本
nice 命令在启动脚本时设置优先级,格式如下:
# 格式:nice -n 优先级 脚本命令(优先级范围:-20 ~ 19)
# 普通用户只能设置 ≥0 的优先级(降低优先级),root 可设置任意值
# 示例:以优先级 10 启动脚本(低优先级,不抢占 CPU)
nice -n 10 ./cpu_intensive.sh > log.txt 2>&1 &
[1] 12349
# 验证优先级(NI 列即为谦让度)
ps -p 12349 -o pid,ppid,ni,cmd
PID PPID NI CMD
12349 12300 10 /bin/bash ./cpu_intensive.sh
2. 用 renice 调整已运行脚本的优先级
若脚本已启动,可通过 renice 命令动态修改其优先级,格式如下:
# 格式:renice -n 新优先级 -p 脚本PID
# 同样,普通用户仅能提高优先级数值(降低实际优先级),root 无限制
# 示例 1:普通用户降低脚本优先级(从 0 改为 5)
# 1. 先启动一个默认优先级的脚本
./cpu_intensive.sh > log.txt 2>&1 &
[1] 12350
# 2. 查看初始优先级(NI=0)
ps -p 12350 -o pid,ppid,ni,cmd
PID PPID NI CMD
12350 12300 0 /bin/bash ./cpu_intensive.sh
# 3. 调整优先级为 5
renice -n 5 -p 12350
12350 (process ID) old priority 0, new priority 5
# 4. 验证调整结果(NI=5)
ps -p 12350 -o pid,ppid,ni,cmd
PID PPID NI CMD
12350 12300 5 /bin/bash ./cpu_intensive.sh
# 示例 2:root 用户提高脚本优先级(从 0 改为 -5)
sudo renice -n -5 -p 12350
12350 (process ID) old priority 5, new priority -5
注意事项:
- 优先级数值越小,实际优先级越高(如 -5 比 0 优先获取 CPU);
- 普通用户若尝试降低优先级数值(提高实际优先级),会提示权限不足:
renice -n -3 -p 12350
renice: failed to set niceness of process ID 12350 (Operation not permitted)
五、自动化:定时运行脚本(at/cron/anacron)
实际运维中,常需要脚本在特定时间执行(如凌晨 3 点备份数据)或定期执行(如每天清理日志)。Linux 提供 at(单次定时)、cron(周期性定时)、anacron(离线补执行)三种工具,覆盖所有定时场景。
1. at 命令:单次定时执行脚本
at 适用于 “仅需执行一次” 的场景(如明天上午 10 点运行脚本),依赖 atd 守护进程(需确保已启动)。
前提:确保 atd 守护进程运行
# 检查 atd 状态(CentOS/RHEL)
systemctl status atd
# 若未运行,启动并设置开机自启
systemctl start atd
systemctl enable atd
# Ubuntu/Debian 系统守护进程名为 atd,命令相同
核心用法:指定时间与脚本
# 格式 1:直接输入命令(适合简单操作)
at 时间
> 脚本命令(如 /home/user/backup.sh)
> Ctrl+D (结束输入,提交作业)
# 格式 2:指定脚本文件(推荐,适合复杂逻辑)
at -f 脚本路径 时间
# 时间格式支持多种写法(灵活度极高)
- 绝对时间:10:30(今天10:30)、10:30 2024-06-10(指定日期)、10:30 AM
- 相对时间:now + 30 minutes(30分钟后)、14:00 tomorrow(明天14点)、now + 1 week(1周后)
- 特殊时间:noon(中午12点)、midnight(凌晨0点)、teatime(下午4点)
示例:明天上午 9 点运行备份脚本
# 1. 准备备份脚本(backup.sh)
#!/bin/bash
echo "=== 开始备份($(date))===" >> /home/user/backup.log
tar -zcf /home/user/data_$(date +%Y%m%d).tar.gz /home/user/data >> /home/user/backup.log 2>&1
echo "=== 备份完成 ===" >> /home/user/backup.log
# 2. 用 at 定时提交
at -f /home/user/backup.sh 9:00 tomorrow
warning: commands will be executed using /bin/sh
job 3 at Mon Jun 10 09:00:00 2024 # 作业号 3,执行时间
# 3. 查看等待中的 at 作业
atq
3 Mon Jun 10 09:00:00 2024 a user # 作业号、时间、用户
# 4. 若需取消,用 atrm 加作业号
atrm 3
关键:处理脚本输出
at 命令默认将脚本的 STDOUT/STDERR 发送到用户邮箱(需系统配置邮件服务),实际使用中更推荐在脚本内手动重定向输出(如上述示例写入 backup.log),避免依赖邮件。
2. cron 命令:周期性定时执行脚本
cron 适用于 “按固定周期重复执行” 的场景(如每天、每周、每月),依赖 crond 守护进程,是 Linux 定时任务的核心工具。
前提:确保 crond 守护进程运行
# CentOS/RHEL 检查与启动
systemctl status crond
systemctl start crond
systemctl enable crond
# Ubuntu/Debian 守护进程名为 cron,命令调整为
systemctl status cron
systemctl start cron
systemctl enable cron
核心:cron 时间表格式
cron 通过 “时间表(crontab)” 定义执行规则,每条规则对应一个定时任务,格式如下:
# 分钟 小时 日 月 星期 命令/脚本路径(绝对路径)
# 0-59 0-23 1-31 1-12 0-7(0/7=周日)
通配符与特殊符号:
- _:匹配所有值(如 “小时” 位写 _ 表示每小时);
- -:指定范围(如 “分钟” 位写 10-20 表示 10 到 20 分钟);
- ,:指定多个值(如 “星期” 位写 1,3,5 表示周一、三、五);
- /:指定步长(如 “分钟” 位写 */15 表示每 15 分钟)。
常用示例(理解 cron 时间表)
需求场景 | cron 时间表规则 | 说明 |
---|---|---|
每天凌晨 3 点执行 | 0 3 * * * /home/user/backup.sh | 分钟 0,小时 3,日 / 月 / 星期任意 |
每天 8:30 和 18:30 执行 | 30 8,18 * * * /home/user/clean.sh | 分钟 30,小时 8 和 18 |
每 15 分钟执行一次 | _/15 _ * * * /home/user/check.sh | 分钟每 15 步,其他任意 |
每周日 22 点执行 | 0 22 * * 0 /home/user/weekly.sh | 分钟 0,小时 22,星期 0(周日) |
每月 1 号和 15 号 10 点 | 0 10 1,15 * * /home/user/monthly.sh | 分钟 0,小时 10,日 1 和 15 |
管理 cron 任务:crontab 命令
每个用户有独立的 cron 时间表,通过 crontab 命令管理,常用选项如下:
选项 | 功能说明 |
---|---|
-l | 列出当前用户的所有 cron 任务 |
-e | 编辑当前用户的 cron 时间表(启动默认编辑器) |
-r | 删除当前用户的所有 cron 任务(谨慎使用) |
-u | 管理其他用户的 cron 任务(仅 root 可用) |
示例:添加 “每天凌晨 3 点备份” 任务
# 1. 编辑 cron 时间表
crontab -e
# 2. 在编辑器中添加以下内容(按 i 进入编辑模式)
0 3 * * * /home/user/backup.sh > /home/user/backup_cron.log 2>&1
# 3. 保存退出(vim 编辑器按 Esc,输入 :wq 回车)
crontab: installing new crontab # 提示:新任务已安装
# 4. 验证任务是否添加成功
crontab -l
0 3 * * * /home/user/backup.sh > /home/user/backup_cron.log 2>&1
# 5. 查看 cron 执行日志(CentOS/RHEL)
tail -f /var/log/cron
# 输出示例(任务执行时会记录)
Jun 10 03:00:01 localhost CROND[12355]: (user) CMD (/home/user/backup.sh > /home/user/backup_cron.log 2>&1)
注意事项:
- 脚本路径必须写绝对路径(cron 执行时环境变量与终端不同,相对路径可能无效);
- 输出重定向建议明确(如上述示例写入 backup_cron.log),便于排查问题;
- 若任务依赖特定环境变量(如 JAVA_HOME),需在脚本内手动定义,或在 cron 命令前加载环境:
# 方式 1:脚本内定义环境变量
# backup.sh 开头添加
export JAVA_HOME=/usr/lib/jvm/jdk1.8.0_301
export PATH=$JAVA_HOME/bin:$PATH
# 方式 2:cron 命令中加载环境
0 3 * * * source /home/user/.bash_profile && /home/user/backup.sh > /home/user/backup_cron.log 2>&1
3. anacron 命令:离线补执行定时任务
cron 依赖系统持续运行 —— 若系统在 cron 任务指定时间处于关机状态(如个人电脑),该次任务会直接跳过,不会补执行。anacron 专门解决此问题,可在系统重启后,自动补执行关机期间错过的定时任务。
核心特性
- 仅处理 “天级及以上” 周期的任务(不支持小时级,因补执行意义不大);
- 依赖 /etc/anacrontab 配置文件,默认管理 /etc/cron.daily(每日)、/etc/cron.weekly(每周)、/etc/cron.monthly(每月)三个目录中的脚本;
- 通过 “时间戳文件”(/var/spool/anacron/ 下)记录上次执行时间,判断是否需要补执行。
示例:让脚本 “每日运行,关机后补执行”
# 1. 准备每日执行的脚本(daily_clean.sh),确保有执行权限
#!/bin/bash
echo "=== 每日清理开始($(date))===" >> /home/user/daily_clean.log
rm -f /home/user/tmp/*.log # 清理临时日志
echo "=== 每日清理完成 ===" >> /home/user/daily_clean.log
chmod +x /home/user/daily_clean.sh
# 2. 将脚本复制到 /etc/cron.daily 目录(anacron 会自动扫描此目录)
sudo cp /home/user/daily_clean.sh /etc/cron.daily/
# 3. 查看 anacron 配置(确认 daily 任务已启用)
cat /etc/anacrontab
# 关键配置如下(默认已存在)
1 5 cron.daily run-parts --report /etc/cron.daily
7 10 cron.weekly run-parts --report /etc/cron.weekly
@monthly 15 cron.monthly run-parts --report /etc/cron.monthly
配置解读:
- 1/7/@monthly:任务周期(1 天 / 7 天 / 每月);
- 5/10/15:系统重启后,延迟 N 分钟开始补执行;
- cron.daily:任务标识(用于日志和时间戳);
- run-parts --report /etc/cron.daily:执行指定目录下的所有脚本(run-parts 是批量执行目录脚本的工具)。
验证补执行效果
- 手动删除 cron.daily 的时间戳文件(模拟 “上次执行时间过期”):
sudo rm /var/spool/anacron/cron.daily
- 重启系统,anacron 会在 5 分钟后补执行 /etc/cron.daily 下的脚本;
- 查看执行日志:
cat /home/user/daily_clean.log
=== 每日清理开始(Mon Jun 10 10:05:00 CST 2024)===
=== 每日清理完成 ===
六、场景化:新 Shell 启动时自动运行脚本
若需要 “每次用户启动新 bash Shell 时” 自动执行脚本(如设置别名、加载环境变量),可利用 bash 的启动文件机制。
1. 区分 “登录 Shell” 与 “非登录 Shell”
bash 启动时加载的文件不同,需先明确两种 Shell 类型:
- 登录 Shell:用户通过密码登录(如 SSH 登录、终端登录),加载顺序:
$HOME/.bash_profile → 若不存在则加载 $HOME/.bash_login → 若仍不存在则加载 $HOME/.profile; - 非登录 Shell:用户在已登录状态下启动新 Shell(如终端中输入 bash),仅加载 $HOME/.bashrc。
实战建议:将需要 “全局生效” 的配置(如别名、环境变量)放入 $HOME/.bashrc,并在 $HOME/.bash_profile 中引用 .bashrc,确保登录 / 非登录 Shell 都能加载:
# 编辑 .bash_profile,添加以下内容
if [ -f ~/.bashrc ]; then
. ~/.bashrc # 加载 .bashrc 中的配置
fi
2. 示例:新 Shell 启动时自动设置别名
# 1. 编辑 .bashrc 文件
vim ~/.bashrc
# 2. 添加别名配置(按 i 编辑,Esc + :wq 保存)
alias ll='ls -l --color=auto' # ll 代替 ls -l
alias rm='rm -i' # rm 时自动提示(防止误删)
alias grep='grep --color=auto' # grep 结果高亮
# 3. 立即生效(无需重启 Shell)
source ~/.bashrc
# 4. 验证:新启动一个 Shell(输入 bash),别名已生效
bash
ll # 等同于 ls -l --color=auto
七、总结:Shell 脚本控制场景与工具选型
需求场景 | 推荐工具 / 命令组合 | 关键注意事项 |
---|---|---|
防止脚本被误中断(如 Ctrl+C) | trap "提示命令" SIGINT SIGTERM | 避开无法捕获的信号(SIGKILL 9、SIGSTOP 19) |
脚本后台运行,不阻塞终端 | ./script.sh > log.txt 2>&1 & | 必须重定向输出,避免与终端命令混叠 |
退出终端后脚本仍运行 | nohup ./script.sh > log.txt 2>&1 & | 用 ps 查看进程,kill 终止脚本 |
管理多个后台 / 暂停脚本 | jobs -l 查看,bg 作业号 恢复后台 | fg 作业号 恢复前台(会阻塞终端) |
降低高耗 CPU 脚本优先级 | nice -n 10 ./script.sh 或 renice -n 10 -p PID | 普通用户仅能提高优先级数值(降低实际优先级) |
单次定时执行脚本(如明天 10 点) | at -f ./script.sh 10:00 tomorrow | 确保 atd 守护进程运行,输出建议重定向 |
周期性定时执行(如每天凌晨 3 点) | crontab -e 添加 0 3 * * * /path/script.sh | 脚本路径用绝对路径,依赖环境需手动定义 |
系统关机后补执行定时任务 | 将脚本放入 /etc/cron.daily 目录 | 仅支持天 / 周 / 月级周期,依赖 anacron 服务 |
每次启动新 Shell 自动执行脚本 | 将配置写入 $HOME/.bashrc | 在 .bash_profile 中引用 .bashrc 确保登录 Shell 生效 |
八、常见问题与排查技巧
在脚本控制过程中,难免会遇到 “任务不执行”“脚本中断” 等问题,以下是高频问题的排查思路:
1. cron 任务不执行?按这 5 步排查
cron 是最易出问题的定时工具,优先检查以下点:
1. 脚本路径是否为绝对路径
cron 执行时的 PATH 环境变量仅包含 /usr/bin:/bin,若脚本用相对路径(如 ./backup.sh)或依赖非系统默认命令(如 python3 可能在 /usr/local/bin),需写绝对路径:
# 错误:依赖相对路径或非默认 PATH 命令
0 3 * * * ./backup.sh # 找不到脚本
0 3 * * * python3 backup.py # 可能找不到 python3
# 正确:全路径写法
0 3 * * * /home/user/backup.sh
0 3 * * * /usr/local/bin/python3 /home/user/backup.py
2. 脚本是否有执行权限
用 chmod +x /path/script.sh 确保脚本可执行,否则 cron 会因权限不足跳过任务:
# 检查权限(需有 x 权限,即执行权限)
ls -l /home/user/backup.sh
-rwxr-xr-x 1 user user 512 Jun 10 09:00 /home/user/backup.sh # 正确(有 x 权限)
3. 输出日志是否有错误信息
务必为 cron 任务添加输出重定向,通过日志定位错误:
# cron 任务配置(重定向 stdout 和 stderr 到日志)
0 3 * * * /home/user/backup.sh > /home/user/backup_cron.err 2>&1
# 查看错误日志(若任务未执行,日志会有明确提示)
cat /home/user/backup_cron.err
/home/user/backup.sh: line 5: tar: command not found # 错误:tar 命令路径问题
4. cron 服务是否正常运行
若服务未启动,所有任务都会失效:
# CentOS/RHEL 检查
systemctl status crond
# Ubuntu/Debian 检查
systemctl status cron
# 若未运行,启动并设置开机自启
systemctl start crond && systemctl enable crond
5. 查看 cron 系统日志
cron 会记录所有任务的执行状态,通过日志确认任务是否被触发:
# CentOS/RHEL 日志路径
tail -f /var/log/cron
# 输出示例(任务被触发但执行失败)
Jun 10 03:00:01 localhost CROND[12355]: (user) CMD (/home/user/backup.sh > /home/user/backup_cron.err 2>&1)
Jun 10 03:00:01 localhost CROND[12354]: (user) MAIL (mailed 1 byte of output; but got status 0x004b, #012)
# Ubuntu/Debian 需先开启日志(编辑 /etc/rsyslog.d/50-default.conf,取消 cron.* 注释)
sudo vim /etc/rsyslog.d/50-default.conf
# 取消以下行注释
cron.* /var/log/cron.log
# 重启 rsyslog 服务
sudo systemctl restart rsyslog
# 查看日志
tail -f /var/log/cron.log
2. 后台脚本被意外终止?检查这 2 点
1. 是否因终端关闭导致(未用 nohup)
若仅用 & 后台运行(未加 nohup),关闭终端会发送 SIGHUP 信号终止脚本。解决方案:
- 重新用 nohup 启动:nohup ./script.sh > log.txt 2>&1 &;
- 若脚本已启动,用 disown 命令让脚本脱离终端(仅当前终端有效):
# 1. 查看后台作业号
jobs -l
[1]+ 12345 Running ./script.sh &
# 2. 让作业脱离终端(作业号 1)
disown -h %1 # -h 选项:忽略 SIGHUP 信号
2. 是否因系统 OOM 杀死进程
若脚本占用 CPU / 内存过高,系统会触发 OOM(Out Of Memory)机制杀死进程。通过 dmesg 或系统日志查看:
# 查看 OOM 日志(搜索 killed process)
dmesg | grep -i "killed process"
# 输出示例(进程 12345 因内存不足被杀死)
[123456.789012] Out of memory: Killed process 12345 (script.sh) total-vm:1024000kB, anon-rss:987654kB, file-rss:0kB, shmem-rss:0kB
解决方案:优化脚本内存占用,或为系统增加 swap 分区。
3. trap 捕获信号无效?检查信号类型
若 trap 无法捕获信号,先确认信号是否属于 “不可捕获型”:
- 不可捕获的信号:SIGKILL(9)(强制终止)、SIGSTOP(19)(强制暂停),任何脚本都无法捕获这两个信号;
- 常见误区:试图捕获 SIGKILL 会无效:
# 错误:SIGKILL 无法捕获
trap "echo '无法捕获'" SIGKILL
kill -9 12345 # 脚本仍会被强制终止,无 echo 输出
若信号可捕获(如 SIGINT)但无效,检查 trap 命令是否在信号发送前执行:
# 错误:trap 定义在信号可能发送之后
sleep 10 # 此期间按 Ctrl+C,trap 未生效
trap "echo '捕获 SIGINT'" SIGINT
# 正确:trap 定义在最前面
trap "echo '捕获 SIGINT'" SIGINT
sleep 10 # 此期间按 Ctrl+C,会触发 trap
九、实战案例:完整的脚本控制方案
以 “每日凌晨 3 点备份 MySQL 数据库” 为例,整合本文所学技巧,实现一个 “安全、可靠、可追溯” 的脚本控制方案:
1. 备份脚本(backup_mysql.sh)
#!/bin/bash
# 功能:备份 MySQL 数据库,支持信号捕获、日志记录、临时文件清理
# 1. 定义环境变量(避免依赖 cron 环境)
export PATH=/usr/local/mysql/bin:/usr/bin:/bin
MYSQL_USER="root"
MYSQL_PASS="your_mysql_password" # 生产环境建议用 ~/.my.cnf 存储密码
BACKUP_DIR="/home/user/mysql_backup"
LOG_FILE="${BACKUP_DIR}/backup.log"
TEMP_FILE=$(mktemp "${BACKUP_DIR}/temp_backup.XXXXXX") # 安全临时文件
# 2. 捕获信号:中断/退出时清理临时文件
trap 'echo "[$(date +%Y-%m-%d %H:%M:%S)] 脚本被中断,清理临时文件..."; rm -f $TEMP_FILE' SIGINT SIGTERM EXIT
# 3. 检查备份目录是否存在
if [ ! -d "$BACKUP_DIR" ]; then
mkdir -p "$BACKUP_DIR"
echo "[$(date +%Y-%m-%d %H:%M:%S)] 创建备份目录:$BACKUP_DIR" >> $LOG_FILE
fi
# 4. 执行备份(输出重定向到临时文件,避免终端刷屏)
echo "[$(date +%Y-%m-%d %H:%M:%S)] 开始备份 MySQL 数据库..." >> $LOG_FILE
mysqldump -u$MYSQL_USER -p$MYSQL_PASS --all-databases > $TEMP_FILE 2>> $LOG_FILE
# 5. 检查备份是否成功
if [ $? -eq 0 ]; then
# 备份成功:重命名临时文件(避免不完整备份)
BACKUP_FILE="${BACKUP_DIR}/mysql_backup_$(date +%Y%m%d_%H%M%S).sql.gz"
gzip $TEMP_FILE # 压缩备份文件
mv "${TEMP_FILE}.gz" "$BACKUP_FILE"
echo "[$(date +%Y-%m-%d %H:%M:%S)] 备份成功,文件:$BACKUP_FILE" >> $LOG_FILE
# 保留最近 30 天的备份,删除旧文件
find "$BACKUP_DIR" -name "mysql_backup_*.sql.gz" -mtime +30 -delete
echo "[$(date +%Y-%m-%d %H:%M:%S)] 删除 30 天前的旧备份" >> $LOG_FILE
else
# 备份失败:记录错误
echo "[$(date +%Y-%m-%d %H:%M:%S)] 备份失败!请查看 mysqldump 输出" >> $LOG_FILE
exit 1
fi
2. 配置 cron 定时任务
# 1. 给脚本添加执行权限
chmod +x /home/user/backup_mysql.sh
# 2. 编辑 cron 任务,每天凌晨 3 点执行
crontab -e
# 添加以下内容(绝对路径 + 错误日志重定向)
0 3 * * * /home/user/backup_mysql.sh 2>> /home/user/mysql_backup_err.log
# 3. 验证任务
crontab -l
3. 保障措施:监控备份状态
为避免备份失败未察觉,可添加 “备份失败邮件告警”(需系统配置邮件服务),在脚本的 “备份失败” 分支添加:
# 备份失败:发送邮件告警
echo "MySQL 备份失败!时间:$(date +%Y-%m-%d %H:%M:%S)" | mail -s "MySQL 备份告警" admin@example.com
十、结语
Shell 脚本控制的核心是 “根据场景选择合适的工具”:
- 短期临时任务:用 & 后台运行,配合 jobs/bg/fg 管理;
- 长期后台任务:用 nohup 脱离终端,确保退出后仍运行;
- 单次定时任务:用 at 命令,简单灵活;
- 周期性任务:用 cron 精确控制,配合 anacron 补执行;
- 防中断需求:用 trap 捕获信号,清理资源。
掌握这些技巧后,你可以轻松应对 Linux 环境下的脚本运行场景,从 “手动执行” 升级为 “自动化、可控化” 的运维模式,大幅提升效率。