真正健壮的 shell 脚本需全局启用 set -euo pipefail 并确保上下文一致,安全展开变量(如 ${var:-}),坚持 posix 兼容写法,正确使用 trap 做可重入清理。

如何让 shell 脚本真正“健壮”:set -euo pipefail 不是摆设
多数人加了 set -euo pipefail 就以为万事大吉,其实它只在特定上下文生效。比如子 shell、if 条件判断块、函数内部没显式启用时,错误仍会静默忽略。
真正起效的前提是:脚本开头第一行(或 shebang 后紧接)就写上,并确保后续所有逻辑都在同一 shell 上下文中执行。
- 别在
$(...)命令替换里依赖外部set——子 shell 会重置选项 -
||和&&连接符会绕过-e,想捕获失败得用if显式判断退出码 -
pipefail让管道中任一命令失败都触发退出,但注意grep -q pattern file | head -1这类常见组合里,head可能因管道提前关闭而报SIGPIPE,这不是业务错误,需用|| true或trap '' PIPE屏蔽
变量展开陷阱:为什么 ${var:-} 比 $var 安全得多
裸写 $var 在未定义或为空时直接变成空字符串,看似无害,但在路径拼接、for 循环或 test 判断中极易引发语法错误或逻辑错位。
比如 cp $src $dst 中若 $src 未设置,实际执行的是 cp $dst,可能误删目标文件。
- 统一用
${var:?}强制非空(报错退出),或${var:-default}提供兜底值 - 数组变量必须用
"${arr[@]}",漏掉引号或写成$arr会丢失空格和多个元素 -
unset var后${var-}(单短横)返回空,${var:-}(双短横)也返回空,但前者不匹配未声明变量,后者连未声明都兜底——日常优先用双短横
跨平台兼容性:bash 特性在 dash/sh 下为何突然失效
Linux 系统默认的 /bin/sh 很可能是 dash,不是 bash。用 #!/bin/bash 能解决,但若脚本被别人以 sh script.sh 方式调用,所有 bash 扩展(如 [[、let、数组、source 的 -r 选项)都会报错。
通过大量实例系统全面地介绍了Linux+PHP+MySQL环境下的网络后台开发技术,详尽分析了近30个典型案例。 本书以培养高级网站建设与管理人才为目标,内容循序渐进,由浅入深,通过大量的实例系统全面地介绍了Linux+PHP+MySQL环境下的网络后台开发技术。 本书详尽分析了近30个典型案例。包括计数器、网站流量统计、留言扳、论坛系统、聊天室、投票与调查、用户管理、新闻发布系统、广告轮播
常见报错如:[: missing ](用了 [[ 却没配对 ]])、Bad substitution(用了 ${var//pattern/repl})、Command not found: declare。
- 检查当前
/bin/sh实际指向:ls -l /bin/sh,Ubuntu/Debian 默认是 dash - 测试兼容性:用
dash -n script.sh静态检查语法,比运行时出错更早发现问题 - 坚持 POSIX 写法:用
[替代[[,用case替代正则匹配,避免数组——除非明确要求 bash
信号与清理:trap 处理 SIGINT/SIGTERM 时最常漏掉的三件事
trap 'rm -f $tmpfile' INT TERM 看似稳妥,但真实场景中 tmpfile 可能根本没创建成功,或已被其他进程占用,甚至 trap 本身在子进程中不继承。
更麻烦的是:SIGPIPE 默认终止进程,但很多脚本里管道下游提前退出(如 head -1),主进程却没收到通知,导致残留。
- trap 函数内务必用
exit显式退出,否则信号处理完继续往下执行 - 临时文件要用
mktemp创建,且在 trap 中检查文件是否存在再删:[[ -f $tmpfile ]] && rm -f $tmpfile - 子进程(如
(sleep 10) &)不会自动继承父进程 trap,需在子 shell 内重新设置,或改用进程组 +kill -- -$$统一清理
信号处理不是加一行 trap 就完事,关键是每个清理动作都要可重入、可跳过、可验证。稍不注意,脚本退出后留下半截文件或僵尸进程,比不加 trap 还难排查。









