Linux shell 脚本编写最佳实践

3次阅读

shell脚本首行应写#!/usr/bin/env bash而非#!/bin/bash以提升跨平台兼容性;所有变量引用必须加双引号;推荐使用$(…)替代反引号;建议添加set -euo pipefail增强健壮性。

Linux shell 脚本编写最佳实践

shell 脚本第一行必须是 #!/usr/bin/env bash,不是 #!/bin/bash

很多脚本在本地跑得好,一上 CI 或不同发行版就报 command not found,根源常在这行。用 /bin/bash 依赖绝对路径,但 Alpine 用 /bin/shmacos 的 Homebrew bash 是 /opt/homebrew/bin/bash,硬写路径等于自设兼容性障碍。

#!/usr/bin/env bash 让系统通过 $PATH 查找,更可靠。但要注意:env 本身也得在 /usr/bin/ 下(绝大多数系统都满足);如果脚本需要严格限定 bash 版本(比如用了 4.4+ 的 declare -v),那就得额外检查 bash --version,不能只靠 shebang。

  • 别用 #!/bin/sh 写 bash 特性(如数组、[[source-r 参数),会静默失败或行为异常
  • CI 环境里,docker 镜像若精简(如 alpine:latest),默认没装 bash —— 此时要么换 #!/bin/sh 并重写逻辑,要么显式 apk add bash
  • 脚本开头加 set -euo pipefail,避免变量未定义、命令失败被忽略、管道中间出错不退出等问题

变量引用不加双引号是 shell 脚本最常见崩溃点

cp $SRC $DST 看似简洁,一旦 $SRC 含空格、通配符或换行符,就会触发单词拆分和路径展开,轻则复制错文件,重则删库跑路。所有变量引用必须包在双引号里:cp "$SRC" "$DST"

例外极少:比如明确要让 shell 展开通配符(ls $PATTERN),或要拆分成多个参数(cmd $ARGS),但这时应改用数组:args=(--verbose --input "$file"); cmd "${args[@]}"

  • [ -n $var ] 错 —— 若 $VAR 为空,变成 [ -n ],条件恒真;应写 [ -n "$VAR" ]
  • for f in $FILES 错 —— 若 $FILES="a b c",会循环三次,但若含空格路径("my file.txt")就裂开;应改用 for f in $FILES 前先设 ifS=$'n',或直接用数组
  • 函数传参也要引号:myfunc "$1" "$2",否则 myfunc "a b" "c" 进入函数后 $1 变成 a$2 变成 b

$(...) 替代反引号 `...`,且避免无必要子 shell

反引号嵌套困难(`echo `date``)、视觉混淆、且 POSIX 兼容性差;$(...) 更易读、支持嵌套($(echo $(date))),所有现代 shell 都支持。

但别滥用:每次 $(...) 都启一个子 shell,频繁调用 datejq 会影响性能。如果只是取当前时间戳,一次就够了:ts=$(date +%s),后面复用 $ts

  • 不要写 if [ "$(grep -q foo file)" ]; then —— grep -q 本身就有退出码,直接 if grep -q foo file; then 更高效、更安全
  • $(cat file) 应简化为 $((bash/zsh 支持),少一次进程开销
  • 解析 json 别用 $(jq '.name' data.json) 多次 —— 一次性读进变量:data=$(jq -r '{name,age}' data.json),再用 name=$(echo "$data" | jq -r '.name')(或直接用 read + jq -r 流式处理)

调试时别只靠 echo,用 set -xPS4 定制输出

set -x 打印每条执行命令及其展开后的结果,比零散 echo "DEBUG: $var" 更系统。但默认输出太吵,尤其带长路径或敏感变量时。

PS4='+ ${BASH_SOURCE##*/}:${LINENO}: ' 可让调试行带上文件名和行号,快速定位;再配合 set -o functrace,还能追踪函数调用链。

  • 临时调试加 set -x,完事加 set +x 关闭,别留在生产脚本里
  • 敏感值(密码、Token)打印前先用 local masked=${TOKEN:0:4}... 处理,或改用 declare -g + set -v 配合日志分级
  • 远程执行脚本(如 ssh host 'bash -s' )时,<code>set -x 输出会混在 stdout 中,建议重定向:set -x 2> /tmp/debug.log

事情说清了就结束。真正难的不是记住这些规则,而是每次敲 if 前下意识多按两个键打上双引号,或者看到反引号就本能地删掉重写。习惯比语法更难改。

text=ZqhQzanResources