内容提要
文章介绍了一个最小且安全的 Bash 脚本模板:以 `#!/usr/bin/env bash` 开头保证兼容性,用 `set -Eeuo pipefail` 实现遇错即退、管道错误检测、未定义变量报错和 ERR 陷阱捕获;模板还包含脚本目录定位、cleanup 收尾、usage 帮助、彩色 msg 输出到 stderr、参数解析及未知选项报错。作者强调模板应简短可靠,复制后只需修改帮助、清理、参数和业务逻辑,并建议用 ShellCheck 检查。
延伸解读
set -Eeuo pipefail 各选项的实际作用
文章通过示例说明:-e 让命令失败时立即退出,避免后续命令误操作;-o pipefail 使管道中任一命令失败即返回非零,防止错误被忽略;-u 在引用未定义变量时报错退出,可用 ${VAR:-默认值} 规避;-E 确保 ERR trap 在函数内也能触发。这些选项组合能显著提升脚本安全性,但作者也提醒需了解反对意见。
脚本目录定位与 cleanup 的实用设计
模板用 script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P) 获取脚本所在目录,避免因工作目录不同导致相对路径失效。cleanup 函数通过 trap 在 EXIT、ERR 等信号时执行,并先清除 trap 防止递归。作者建议将临时文件清理等收尾工作放在这里,也可在运行中手动调用。
参数解析的取舍与可移植性限制
parse_params 手动解析 flags、命名参数和位置参数,并对未知选项报错退出。作者指出它不支持合并的单字母选项(如 -ab),且 getopt 在 macOS 行为不同、getopts 不支持长参数,因此选择手写循环。模板在 macOS(bash 3.2)及多个 Linux 发行版测试通过,但不适用于缺少 Bash 的 Alpine Linux(使用 ash)。
输出规范与工具辅助
msg 函数将日志和提示输出到 stderr,并仅在交互式终端且未设置 NO_COLOR 时启用颜色,避免污染 stdout 的命令输出。作者引用 12 Factor CLI Apps 强调 stdout 用于数据、stderr 用于消息。此外,建议使用支持 ShellCheck 的 IDE 编写脚本,以提前发现常见错误。
Q&A
写bash脚本时,怎么让脚本在遇到错误时立刻退出?
在脚本开头加上 `set -Eeuo pipefail`。其中 `-e` 让命令失败时立即退出,`-o pipefail` 让管道中任一命令失败即视为失败,`-u` 让使用未定义变量时报错退出,`-E` 确保 ERR 陷阱能被捕获。
bash脚本里 set -Eeuo pipefail 每个选项具体是什么意思?
`-e`:命令返回非零时立即退出;`-o pipefail`:管道中任一命令失败则整个管道失败;`-u`:使用未定义变量时报错退出;`-E`:使 ERR 陷阱在函数或子 shell 中也能被触发。
为什么bash脚本要用 #!/usr/bin/env bash 而不是 #!/bin/bash?
使用 `#!/usr/bin/env bash` 是为了更好的兼容性,它会从环境变量 PATH 中查找 bash 解释器,而不是硬编码 `/bin/bash` 路径,这样在不同系统上更通用。
bash脚本里怎么获取脚本自身所在的目录?
使用 `script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P)`。这行代码能可靠地定位脚本所在目录,之后可以用 `$script_dir` 引用同目录下的文件,避免因工作目录不同而出错。
bash脚本中如何优雅地处理参数解析和未知选项?
模板中的 `parse_params()` 函数用 `while` 循环和 `case` 语句解析参数,支持标志(如 `-f`)、命名参数(如 `-p`)和位置参数。遇到未知选项时用 `die "Unknown option: $1"` 报错退出,防止后续错误执行。
bash脚本里怎么输出带颜色的日志信息,同时保证输出到stderr?
模板提供了 `setup_colors()` 和 `msg()` 函数。`msg()` 用 `echo >&2 -e` 将消息输出到 stderr,并支持颜色变量(如 `${RED}`)。颜色仅在 stderr 是交互式终端且未设置 `NO_COLOR` 时启用,否则自动禁用。
bash脚本模板中 cleanup 函数是做什么的?怎么用?
`cleanup()` 函数用于脚本收尾,比如删除临时文件。通过 `trap cleanup SIGINT SIGTERM ERR EXIT` 注册,在脚本退出或收到信号时自动执行。函数内先用 `trap - SIGINT SIGTERM ERR EXIT` 取消陷阱,避免递归调用。
这个bash脚本模板复制后需要修改哪些地方?
复制模板后,只需修改四个部分:`usage()` 中的脚本说明、`cleanup()` 中的清理内容、`parse_params()` 中的参数定义,以及最后的实际脚本逻辑。其余部分保持原样即可。