一、开头三行
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
逐条解释:
#!/usr/bin/env bash:用环境里的 bash,比写死/bin/bash更可移植(macOS 自带的 bash 是 3.2,要新特性需装新版)。set -e:命令失败(返回非 0)立即退出,避免「出错了还继续往下跑」。set -u:使用未定义变量时报错,避免rm -rf $DIR/里DIR为空导致删根目录。set -o pipefail:管道中任一环节失败即视为失败(默认只看最后一个命令)。IFS:改分隔符,避免for f in $(ls)被空格拆错(更好的是根本不用ls)。
这三行能挡掉大部分「脚本把事搞砸」的事故。
二、变量与参数必须加引号
# 危险:文件名含空格会被拆成两个参数
cp $file /backup/
# 安全
cp "$file" /backup/
"$var" 与 $var 的区别在于「是否保留为一个整体」。特别注意:
[ -f $file ]在$file为空时会变成[ -f ],语法错误;写[ -f "$file" ]。rm -rf "$dir"/与rm -rf "$dir/"在$dir为空时行为不同(后者会删/)。删除操作前先校验[ -n "$dir" ]。
三、$@、$*、$#
echo "$#" # 参数个数
echo "$@" # 所有参数,各自独立(推荐)
echo "$*" # 所有参数,拼成一个字符串
echo "$1" # 第一个参数
"$@" 会保留每个参数的边界,"$*" 会用 IFS 拼起来。遍历参数用:
for arg in "$@"; do
echo "$arg"
done
**不要写 for arg in $***,含空格的参数会被拆开。
默认值与校验:
NAME="${1:-default}" # 为空则用默认值
: "${API_KEY:?必须设置 API_KEY}" # 为空则报错退出
四、[ ] 还是 [[ ]]
[[ ]] 是 bash 的扩展,更安全、更好用:
| 场景 | [ ] | [[ ]] | ||
|---|---|---|---|---|
| 字符串比较 | [ "$a" = "$b" ] | [[ $a == $b ]] | ||
| 模式匹配 | 不支持 | [[ $a == pre* ]] | ||
| 正则 | 不支持 | [[ $a =~ ^[0-9]+$ ]] | ||
| 逻辑运算 | -a / -o(易错) | && / ` | ` | |
| 空变量 | 必须加引号 | 不加强制但要小心 |
用 [[ ]] 时变量可以不引号,但养成加引号的习惯仍然更稳。POSIX sh 环境(如 Alpine 的 /bin/sh)不支持 [[ ]],脚本要兼容就老老实实用 [ ]。
五、函数与返回值
backup() {
local src="$1"
local dst="$2"
[ -d "$src" ] || { echo "源目录不存在: $src" >&2; return 1; }
tar -czf "$dst" "$src"
}
if ! backup /data /tmp/data.tgz; then
echo "备份失败" >&2
exit 1
fi
要点:
local声明局部变量,避免污染全局。- 返回值是 0–255 的退出码,不是数据。要返回数据用
echo+ 命令替换。 - 错误信息输出到
>&2(stderr),别混进正常输出。
六、调试
bash -x script.sh # 打印每条执行的命令
bash -n script.sh # 只做语法检查,不执行
set -x / set +x # 在脚本内局部开启/关闭
配合 PS4='+ $LINENO: ' 可以显示行号,定位更快。
另一个技巧是 shellcheck(静态检查工具),能提前发现引号缺失、无用变量、[ ] 用法错误等问题,CI 里跑一遍很值。
七、几个易错点
cd失败后仍继续:cd /some/dir || exit 1。- 临时文件不安全:用
mktemp而不是写死/tmp/tmp.txt。 - 信号处理:
trap 'rm -f "$tmp"' EXIT保证退出时清理。 - 管道里的变量:
cmd | while read x; do total=$((total+1)); done在子 shell 里执行,total出了循环就没了。用进程替换或shopt -s lastpipe。 - 数字比较:
[ "$a" -eq 1 ]是数字,[ "$a" = "1" ]是字符串。[[ 01 -eq 1 ]]为真,[[ 01 == 1 ]]为假。
小结
开头 #!/usr/bin/env bash + set -euo pipefail;变量和参数一律加引号;遍历用 "$@";条件用 [[ ]](或兼容性要求高时用 [ ]);调试 bash -x,检查用 shellcheck。