在运维场景里待久了,我发现脚本运行时总有 “意外状况”—— 比如手动按了 Ctrl+C 把脚本打断,或者需要让脚本在后台跑一整晚却怕终端断开。这时候 “脚本控制” 就像给脚本装了 “应急系统” 和 “调度中心”,既能应对突发情况,又能灵活安排任务的运行方式。我还记得十年前,团队跑一个数据同步脚本,因为没人盯着,终端断开导致脚本中途停了,白白浪费了 4 小时;后来用了脚本控制的技巧,不仅能防止意外中断,还能让脚本在后台稳定运行,这才明白 “会写脚本是基础,会控制脚本才是真本事”。

脚本控制
1. 信号处理
我们在操作电脑时,经常会给程序发 “信号”—— 比如按 Ctrl+C 终止程序,关闭终端给程序发 “断开信号”,这些信号就像 “紧急指令”,程序收到后会有默认反应。但有时候我们不想让脚本 “收到信号就傻跑”,比如按了 Ctrl+C,希望脚本先保存数据再退出,这时候就需要 “信号处理”,用trap命令给脚本装个 “自定义响应机制”。
(1)常见信号
Shell 里有很多预设信号,就像交通信号灯,不同信号代表不同 “指令”,最常用的有四个:
- 1 号信号(HUP):“挂起信号”,比如关闭终端时,终端会给里面运行的脚本发这个信号,脚本默认会退出。早年我在服务器上跑日志分析脚本,有时候远程连接断开,再连上去发现脚本停了,就是因为收到了 HUP 信号。
- 2 号信号(INT):“中断信号”,也就是我们常按的 Ctrl+C,脚本默认会立刻停止。比如脚本在循环输出内容,按 Ctrl+C 就会中断循环,这是最常用的 “手动停脚本” 方式。
- 9 号信号(KILL):“强制终止信号”,这是 “终极指令”,脚本收到后会立刻被杀死,而且无法通过trap捕获 —— 就像 “紧急断电”,不管程序在做什么,都会马上停。如果遇到脚本卡死,用kill -9 进程号就能强制关掉,不过要谨慎用,可能会丢数据。
- 15 号信号(TERM):“正常终止信号”,kill命令默认发的就是这个信号,脚本收到后会正常退出(比如先执行清理操作),比 9 号信号 “温柔” 得多。比如用kill 1234(1234 是进程号)停脚本,脚本会先做完手头的事再退出,适合 “优雅停服务”。
(2)trap 命令
trap命令就像给脚本装了个 “信号接收器”,能指定 “收到某个信号后该做什么”。最常用的场景是 “捕获 Ctrl+C”,比如脚本在备份数据,按了 Ctrl+C 后,希望先提示 “正在保存数据,请勿中断”,再继续保存:
# 捕获INT信号(Ctrl+C)
trap "echo -e '\n正在保存数据,请勿中断...'; sleep 3; echo '数据保存完成'" INT
# 模拟备份过程
echo "开始备份数据..."
for ((i=1; i<=5; i++)); do
echo "备份进度:$((i*20))%"
sleep 2
done
echo "备份完成"
运行这个脚本,按 Ctrl+C 时,不会立刻中断,而是先执行trap里的命令:提示保存、等 3 秒、显示保存完成,再退出 —— 这样就避免了按错 Ctrl+C 导致数据丢失。当年我写数据库备份脚本时,就加了这个逻辑,有次同事不小心按了 Ctrl+C,脚本还是把已备份的数据存好了,没造成损失。
trap还能捕获 “脚本退出”(不管是正常退出还是被信号中断),用EXIT作为信号就行。比如脚本运行时创建了临时文件,希望退出时自动删除,就可以:
# 创建临时文件
temp=$(mktemp)
echo "临时文件路径:$temp"
# 捕获EXIT信号,退出时删除临时文件
trap "rm -f $temp; echo '临时文件已删除'" EXIT
# 模拟脚本逻辑
sleep 5
echo "脚本正常退出"
不管是等 5 秒正常退出,还是按 Ctrl+C 中断,脚本都会执行trap里的rm -f $temp,把临时文件删掉 —— 不用手动清理,特别省心。我当年写临时日志处理脚本时,就靠这个技巧,避免了服务器上堆积大量临时文件。
2. 运行模式与作业控制
有时候脚本需要 “灵活安排运行方式”—— 比如让脚本在后台跑,不影响当前终端操作;或者让脚本脱离终端,就算远程连接断了也能继续跑;还能调整脚本的 “优先级”,让重要的脚本先占用资源。这些就是 “运行模式与作业控制” 要解决的问题,就像给任务安排 “工位”:有的任务在 “前台工位”(能看到实时输出),有的在 “后台工位”(默默干活),有的在 “独立工位”(不受终端影响)。
(1)后台运行
如果脚本要跑很久(比如几小时),总不能一直盯着终端,这时候加个&就能让脚本 “后台运行”,终端还能继续干别的事。比如./backup.sh &,运行后会显示脚本的进程号(如[1] 1234),其中[1]是 “作业号”,1234是进程号。
后台运行的脚本,用jobs命令就能查看状态 —— 比如jobs会显示 “[1]+ Running ./backup.sh &”,表示 1 号作业正在运行;如果脚本输出内容,会默认打印到当前终端,有时候会干扰操作,这时候可以把输出重定向到文件,比如./backup.sh > backup.log 2>&1 &,让输出都进日志文件,终端就干净了。
要是想把后台作业调回前台,用fg %作业号,比如fg %1就把 1 号作业拉到前台,能看到实时输出;如果前台作业想放回后台,先按Ctrl+Z暂停作业,再用bg %作业号,比如bg %1,作业就会在后台继续运行。当年我在服务器上跑数据同步脚本,一开始在前台跑,后来想干别的,就按Ctrl+Z暂停,再bg %1放后台,既不影响同步,又能继续操作终端。
(2)非控制台运行
后台运行有个问题:如果关闭终端(比如远程连接断开),脚本会收到 HUP 信号,默认会退出。这时候就需要nohup命令,让脚本 “脱离终端”,就算终端关了也能继续跑。比如nohup ./backup.sh &,运行后会提示 “nohup: ignoring input and appending output to 'nohup.out'”,表示脚本的输出会默认存到nohup.out文件里。
如果想指定输出文件,就加重定向,比如nohup ./backup.sh > mylog.log 2>&1 &,输出会存到mylog.log,而不是默认的nohup.out。当年团队跑 “夜间数据备份脚本”,就是用nohup让脚本在后台跑一整晚,就算远程连接断了,第二天看mylog.log就知道备份有没有成功,不用熬夜盯着。
(3)优先级调整
服务器上可能同时跑多个脚本,有的重要(比如实时监控脚本),有的不紧急(比如日志归档脚本),这时候需要给它们 “分资源”—— 重要的脚本优先用 CPU,不紧急的少占点资源,这就是 “优先级调整”,用nice和renice命令实现。
nice 命令:在启动脚本时设置优先级,格式是nice -n 优先级值 脚本。优先级值的范围是-20到19,值越小,优先级越高(越容易抢到 CPU 资源)。比如nice -n 5 ./log_archive.sh,给日志归档脚本设优先级 5(较低),让它少占资源;如果是监控脚本,就用nice -n -5 ./monitor.sh,设优先级 - 5(较高),让它优先运行。不过要注意,普通用户只能设0及以上的优先级,-20到-1需要 root 权限 —— 就像普通员工只能用普通工位,VIP 工位需要管理员授权。
renice 命令:调整已经在运行的脚本的优先级,格式是renice 新优先级值 -p 进程号。比如发现日志归档脚本(进程号 1234)占 CPU 太多,就用renice 10 -p 1234,把它的优先级从 5 调到 10(更低),让给其他脚本;如果监控脚本(进程号 5678)抢不到资源,就用renice -10 -p 5678(需要 root),提高它的优先级。当年服务器上同时跑备份和监控脚本,备份脚本占了太多 CPU,监控脚本反应变慢,用renice把备份脚本的优先级调低后,监控脚本就恢复正常了。
最后小结
梳理完脚本控制的内容,我想起早年踩过的坑 —— 有次在服务器上跑数据迁移脚本,没加信号处理,按错 Ctrl+C 导致迁移中断,还丢了部分数据;还有次没用车nohup,远程连接断了,脚本停了,只能重新跑,浪费了半天时间。后来掌握了trap、nohup这些技巧,才明白 “写脚本不仅要让它跑得起来,更要控得住”—— 能应对意外中断,能灵活安排运行方式,才是 “靠谱的脚本”。
其实脚本控制的核心,是 “站在运维的角度想问题”:运维人员怕什么?怕脚本意外停了丢数据,怕终端断了任务没完成,怕不重要的脚本占太多资源。所以trap解决 “丢数据”,nohup解决 “终端断任务停”,nice/renice解决 “资源抢占”—— 每一个命令背后,都是对运维场景的理解。
如今回头看,从基础构建、数据处理、结构化命令、交互呈现,到现在的脚本控制,Shell 脚本的每一步学习,都是 “从技术到场景” 的落地。它或许没有复杂的语法,但每一个命令都能精准解决运维中的实际问题 —— 这也是为什么 Shell 能在 IT 行业里 “活” 这么多年,能成为运维工程师的 “左手”。而对我们这些老程序员来说,把这些 “控脚本” 的技巧讲清楚,让更多人少踩坑、提高效率,也是对技术传承的一份责任。未完待续..........


被折叠的 条评论
为什么被折叠?



