写出健壮的脚本
目标
逐一实现可以部署到生产服务器的脚本应具备的条件:不隐藏失败、保证收尾清理,并且重复执行也安全。
为什么重要
shell 默认非常宽容:不存在的变量会被当作空字符串,管道中间的失败会在最后一条命令成功时被掩盖,脚本即使异常退出也会留下临时文件。这种宽容会造成‘每天都报告成功的备份其实是 0 字节’之类的事故。set -euo pipefail 用于在发现问题的位置立即停止,trap ... EXIT 则保证无论通过哪条路径结束都会执行清理。二者配合使用才有意义。
步骤
- 创建
/root/robust/strict.sh。在开头加入set -euo pipefail,接收一个参数并原样输出。strict.sh hello应输出hello并以 0 结束;不带参数运行时必须以非 0 退出码结束。 - 创建
/root/robust/pipe.sh。参数为ok时运行成功的管道,参数为broken时运行前一阶段失败的管道。broken时退出码必须非 0。 - 创建
/root/robust/tmpwork.sh。用mktemp创建临时文件,将路径输出到标准输出的一行中,并在退出时通过trap确保删除。传入参数fail时要以失败结束,但此时也不能留下临时文件。 - 创建
/root/robust/once.sh。运行后工作约 3 秒并以 0 结束。如果已有实例正在运行,再次执行时应提示already running,并以退出码 9 结束。前一个实例结束后必须能再次正常运行。 - 创建
/root/robust/count.sh <디렉터리>。只输出该目录直属的普通文件数量。目录路径和文件名中即使有空格也必须正确;没有参数时必须以非 0 退出码结束。 - 创建
/root/robust/backup.sh <원본디렉터리> <결과파일>。- 没有参数时,向标准错误输出包含
usage的提示,并以退出码64结束 - 原路径不存在时,以退出码
66结束 - 正常时,创建包含子目录的 tar.gz 文件,并以退出码
0结束
- 没有参数时,向标准错误输出包含
- 创建
/root/robust/retry.sh <횟수> <명령...>。命令失败时最多重试指定次数,每次尝试之间至少等待 1 秒,成功后立即停止。每次尝试都要留下包含attempt一词的日志。 - 创建
/root/robust/audit.sh <스크립트경로>。检查目标脚本是否包含 shebang 和严格模式声明,输出strict=yes|no与shebang=yes|no两行。两者均为 yes 时退出码为 0,否则为非 0。文件不存在时也必须返回非 0。
提示
- 如果不在
trap处理程序开头用local rc=$?保存退出码,并在最后用exit "$rc"恢复,trap 就会吞掉退出码。 - 使用
find "$d" -maxdepth 1 -type f可以只统计顶层普通文件。 - 常见错误 1:第 4 步只创建锁文件却不删除,会让后续运行永远被阻塞。
- 常见错误 2:第 7 步用
$*重新执行命令,会破坏包含空格的参数。
启用严格模式
创建 /root/robust/strict.sh。在开头加入 set -euo pipefail,接收一个参数并原样输出。strict.sh hello 应输出 hello 并以 0 结束;不带参数运行时必须以非 0 退出码结束。
只需一行 set -euo pipefail。启用 -u 后,一旦读取不存在的 $1,脚本就应立即终止。
传播管道失败
创建 /root/robust/pipe.sh。参数为 ok 时运行成功的管道,参数为 broken 时运行前一阶段失败的管道。broken 时退出码必须非 0。
默认 shell 只看最后一条命令的状态。请确保前一阶段失败时,整个脚本也以失败结束。
确保删除临时文件
创建 /root/robust/tmpwork.sh。用 mktemp 创建临时文件,将路径输出到标准输出的一行中,并在退出时通过 trap 确保删除。传入参数 fail 时要以失败结束,但此时也不能留下临时文件。
trap ... EXIT 在正常和错误退出时都会执行。请用 mktemp 创建临时文件。
防止重复运行
创建 /root/robust/once.sh。运行后工作约 3 秒并以 0 结束。如果已有实例正在运行,再次执行时应提示 already running,并以退出码 9 结束。前一个实例结束后必须能再次正常运行。
使用 flock 或基于 mkdir 的锁。第二次运行必须立即以退出码 9 结束,前一个实例结束后锁必须释放。
安全处理含空格的路径
创建 /root/robust/count.sh <디렉터리>。只输出该目录直属的普通文件数量。目录路径和文件名中即使有空格也必须正确;没有参数时必须以非 0 退出码结束。
路径和文件名中都有空格。如果不引用变量,计数就会出错。
用法与退出码约定
创建 /root/robust/backup.sh <원본디렉터리> <결과파일>。
- 没有参数时,向标准错误输出包含
usage的提示,并以退出码64结束 - 原路径不存在时,以退出码
66结束 - 正常时,创建包含子目录的 tar.gz 文件,并以退出码
0结束
用法提示写入标准错误,不同情况返回不同退出码。64 表示用法错误,66 表示输入不存在。
重试辅助脚本
创建 /root/robust/retry.sh <횟수> <명령...>。命令失败时最多重试指定次数,每次尝试之间至少等待 1 秒,成功后立即停止。每次尝试都要留下包含 attempt 一词的日志。
要原样重新执行命令,需要使用 "$@"。成功后立即停止,并且每次尝试之间要等待。
脚本审计工具
创建 /root/robust/audit.sh <스크립트경로>。检查目标脚本是否包含 shebang 和严格模式声明,输出 strict=yes|no 与 shebang=yes|no 两行。两者均为 yes 时退出码为 0,否则为非 0。文件不存在时也必须返回非 0。
读取另一个脚本文件,判断是否含有 shebang 和严格模式。还必须处理文件不存在的情况。