Shell脚本如何避免“一次性”陷阱?

话题来源: Shell脚本进阶:编写健壮、可维护的自动化运维工具

不知道你有没有过这种经历?深夜,服务器突然报警,你手忙脚乱地翻出半年前写的一个部署脚本,一边祈祷一边运行。结果呢?要么因为一个变量没定义直接退出,留下一堆没清理的临时文件;要么悄无声息地失败了,你瞪着屏幕,完全不知道它卡在了哪一步。这种脚本,我称之为“一次性”脚本——用一次,就再也不想碰第二次,像极了用完就扔的一次性餐具。

别让脚本“静悄悄”地失败

我觉得,“一次性”脚本最大的罪过就是“沉默”。它失败了,却不告诉你为什么。我记得有一次,一个备份脚本因为磁盘空间不足挂了,但日志里只有一行“备份失败”。我花了半小时查权限、查网络,最后发现是磁盘满了,那一刻真的想把自己写的脚本给删了。

后来我学乖了,给每个脚本都强行加上“嘴巴”。不是那种简单的 echo,而是一个有模有样的日志函数。就像这样:

logme() {
  local level=$1
  local msg=$2
  echo “[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $msg” | tee -a /var/log/my_script.log
}
# 用起来
logme "INFO" "开始拉取代码..."
logme "ERROR" "克隆仓库失败,检查网络或权限!"

这招太管用了。现在脚本每走一步都会“自言自语”,成功还是失败,原因是什么,一目了然。排查问题从“猜谜游戏”变成了“阅读理解”,幸福感飙升。

给脚本上个“紧箍咒”

另一个让我头疼的问题是“放任自流”。脚本里拼错一个变量名,比如把 $DEPLOY_PATH 写成 $DEPLOY_PTAH,结果变量变成了空字符串,命令可能就默默地执行到了奇怪的地方,后果不堪设想。

我的解决办法是,在脚本开头念一句“咒语”:set -euo pipefail。这可不是摆设。

  • -e:任何命令失败(返回非0)就立刻退出,不往下瞎跑了。
  • -u:用了未定义的变量?马上报错!把拼写错误扼杀在摇篮里。
  • -o pipefail:管道命令里,只要有一环出错,整个管道就算失败。再也不会出现“grep something | wc -l”里 grep 没找到东西,但 wc 依然输出个0的尴尬情况。

这就像给脚本戴上了紧箍咒,让它守规矩,一有邪念(错误)就头疼停下,安全多了。

别忘了设置“逃生通道”

脚本可能会被意外中断(比如你按了 Ctrl+C),也可能自己出错退出。如果它正在更新文件,突然停下,会不会留下一个半成品,导致服务起不来?太有可能了。

所以,我习惯给脚本设置一个“逃生通道”,用 trap 命令。不管脚本是怎么退出的,是成功、失败还是被强行杀掉,在退出前,都执行一下清理工作。

cleanup() {
  echo “正在清理临时文件...”
  rm -rf /tmp/myscript_temp.*
  # 甚至可以尝试回滚到上一个版本
  echo “清理完毕。”
}
# 注册这个函数,在脚本退出时调用
trap cleanup EXIT

有了这个,就算脚本中途夭折,也能尽量把现场打扫干净,不给后续操作埋雷。这感觉,就像有个靠谱的队友帮你收尾,无比安心。

让脚本“活”得更久一点

说到底,避免“一次性”陷阱,就是要把脚本当成一个会长期服役的“工具”来打磨,而不是随手一扔的“便签”。给它清晰的日志,它才能告诉你发生了什么。给它严格的约束,它才不会跑偏。给它善后的能力,它才不会制造混乱。

多花十分钟写这些“防御性”代码,未来可能省下你十个小时的调试时间。下次再写脚本的时候,不妨试试看,你会发现,一个可靠的工具,用起来手感是完全不一样的。

评论