使用场景
- 编写或审查用于自动化、CI/CD 或运维的 Bash 脚本
- 加固 Shell 脚本的安全性和可移植性
不适用场景
- 需要 POSIX-only shell 而不使用 Bash 特性
- 任务需要更高级的语言来处理复杂逻辑
- 需要原生 Windows 脚本(PowerShell)
指南
- 定义脚本输入、输出和失败模式。
- 应用严格模式和安全的参数解析。
- 使用防御性模式实现核心逻辑。
- 使用 Bats 和 ShellCheck 添加测试和代码检查。
安全
- 将输入视为不可信;避免 eval 和不安全的 globbing。
- 在执行破坏性操作前优先使用 dry-run 模式。
重点领域
- 带严格错误处理的防御性编程
- POSIX 合规和跨平台可移植性
- 安全的参数解析和输入验证
- 健壮的文件操作和临时资源管理
- 进程编排和管道安全
- 生产级日志记录和错误报告
- 使用 Bats 框架进行全面测试
- 使用 ShellCheck 进行静态分析,使用 shfmt 进行格式化
- 现代 Bash 5.x 特性和最佳实践
- CI/CD 集成和自动化工作流
方法
- 始终使用
set -Eeuo pipefail严格模式并正确设置错误陷阱 - 对所有变量扩展加引号以防止单词分割和 globbing 问题
- 优先使用数组和正确的迭代方式,而非
for f in $(ls)等不安全模式 - 在 Bash 中使用
[[ ]]进行条件判断,POSIX 合规时回退到[ ] - 使用
getopts和用法函数实现完整的参数解析 - 使用
mktemp安全创建临时文件和目录,并设置清理陷阱 - 优先使用
printf而非echo以获得可预测的输出格式 - 使用命令替换
$()代替反引号以提高可读性 - 实现带时间戳和可配置详细程度的结构化日志
- 设计幂等脚本并支持 dry-run 模式
- 在 Bash 4.4+ 中使用
shopt -s inherit_errexit改善错误传播 - 使用
IFS=$'\n\t'防止空格导致的意外单词分割 - 使用
: "${VAR:?message}"验证必需的环境变量 - 选项解析以
--结束,使用rm -rf -- "$dir"进行安全操作 - 支持
--trace模式,使用set -x进行详细调试 - 使用
xargs -0配合 NUL 边界安全编排子进程 - 使用
readarray/mapfile安全地从命令输出填充数组 - 实现健壮的脚本目录检测:
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" - 使用 NUL 安全模式:
find -print0 | while IFS= read -r -d '' file; do ...; done
兼容性与可移植性
- 使用
#!/usr/bin/env bashshebang 实现跨系统可移植性 - 在脚本开头检查 Bash 版本:
(( BASH_VERSINFO[0] >= 4 && BASH_VERSINFO[1] >= 4 ))用于 Bash 4.4+ 特性 - 验证必需的外部命令是否存在:
command -v jq &>/dev/null || exit 1 - 检测平台差异:
case "$(uname -s)" in Linux*) ... ;; Darwin*) ... ;; esac - 处理 GNU 与 BSD 工具差异(如
sed -ivssed -i '') - 在所有目标平台上测试脚本(Linux、macOS、BSD 变体)
- 在脚本头部注释中记录最低版本要求
- 为平台特定功能提供回退实现
- 尽可能使用 Bash 内置功能而非外部命令以提高可移植性
- 当需要 POSIX 合规时避免 bashisms,使用 Bash 特定功能时需记录
可读性与可维护性
- 在脚本中使用长选项以提高清晰度:使用
--verbose而非-v - 采用一致的命名:函数/变量使用 snake_case,常量使用 UPPER_CASE
- 使用注释块添加章节标题来组织相关函数
- 保持函数在 50 行以内;将较大的函数重构为更小的组件
- 将相关函数放在一起,并使用描述性的章节标题
- 使用描述性的函数名称说明用途:
validate_input_file而非check_file - 为不明显的逻辑添加内联注释,避免陈述显而易见的内容
- 保持一致的缩进(2 或 4 个空格,切勿混合使用制表符和空格)
- 将左大括号放在同一行以保持一致性:
function_name() { - 使用空行分隔函数内的逻辑块
- 在头部注释中记录函数参数和返回值
- 将魔法数字和字符串提取到脚本顶部的命名常量中
安全与安全模式
- 使用
readonly声明常量以防止意外修改 - 对所有函数变量使用
local关键字以避免污染全局作用域 - 为外部命令实现
timeout:timeout 30s curl ...防止挂起 - 在操作前验证文件权限:
[[ -r "$file" ]] || exit 1 - 尽可能使用进程替换
<(command)代替临时文件 - 在命令或文件操作中使用用户输入前进行消毒
- 使用模式匹配验证数字输入:
[[ $num =~ ^[0-9]+$ ]] - 切勿对用户输入使用
eval;使用数组进行动态命令构建 - 为敏感操作设置严格的 umask:
(umask 077; touch "$secure_file") - 记录安全相关操作(认证、权限更改、文件访问)
- 使用
--分隔选项和参数:rm -rf -- "$user_input" - 在使用环境变量前进行验证:
: "${REQUIRED_VAR:?not set}" - 显式检查所有安全关键操作的退出码
- 使用
trap确保即使异常退出也能进行清理
性能优化
- 避免在循环中使用子 shell;使用
while read而非for i in $(cat file) - 优先使用 Bash 内置功能而非外部命令:
[[ ]]代替test,${var//pattern/replacement}代替sed - 批量操作而非重复的单个操作(如一个
sed携带多个表达式) - 使用
mapfile/readarray高效地从命令输出填充数组 - 避免重复的命令替换;将结果存储在变量中一次
- 使用算术扩展
$(( ))代替expr进行计算 - 优先使用
printf而非echo进行格式化输出(更快更可靠) - 使用关联数组进行查找而非重复的 grep
- 对大文件逐行处理而非将整个文件加载到内存
- 当操作相互独立时使用
xargs -P进行并行处理
文档标准
- 实现
--help和-h标志显示用法、选项和示例 - 提供
--version标志显示脚本版本和版权信息 - 在帮助输出中包含常见用例的使用示例
- 记录所有命令行选项及其用途说明
- 在用法消息中清晰列出必需参数和可选参数
- 记录退出码:0 表示成功,1 表示一般错误,特定代码表示特定失败
- 包含先决条件部分,列出所需的命令和版本
- 添加头部注释块,包含脚本用途、作者和修改日期
- 记录脚本使用或需要的环境变量
- 在帮助中提供常见问题的故障排除部分
- 使用
shdoc从特殊注释格式生成文档 - 使用
shellman创建 man 页面以集成到系统中 - 对于复杂脚本,使用 Mermaid 或 GraphViz 包含架构图
现代 Bash 特性 (5.x)
- Bash 5.0:关联数组改进,
${var@U}大写转换,${var@L}小写 - Bash 5.1:增强的
${parameter@operator}转换,compatshopt 选项用于兼容性 - Bash 5.2:
varredir_close选项,改进的exec错误处理,EPOCHREALTIME微秒精度 - 使用现代特性前检查版本:
[[ ${BASH_VERSINFO[0]} -ge 5 && ${BASH_VERSINFO[1]} -ge 2 ]] - 使用
${parameter@Q}进行 shell 引用输出(Bash 4.4+) - 使用
${parameter@E}进行转义序列扩展(Bash 4.4+) - 使用
${parameter@P}进行提示符扩展(Bash 4.4+) - 使用
${parameter@A}进行赋值格式(Bash 4.4+) - 使用
wait -n等待任意后台作业(Bash 4.3+) - 使用
mapfile -d delim处理自定义分隔符(Bash 4.4+)
CI/CD 集成
- GitHub Actions:使用
shellcheck-problem-matchers进行内联注释 - Pre-commit hooks:配置
.pre-commit-config.yaml包含shellcheck、shfmt、checkbashisms - 矩阵测试:在 Linux 和 macOS 上测试 Bash 4.4、5.0、5.1、5.2
- 容器测试:使用官方 bash:5.2 Docker 镜像进行可复现测试
- CodeQL:启用 shell 脚本扫描以检测安全漏洞
- Actionlint:验证使用 shell 脚本的 GitHub Actions 工作流文件
- 自动发布:自动标记版本并生成变更日志
- 覆盖率报告:跟踪测试覆盖率,覆盖率回退时失败
- 示例工作流:
shellcheck *.sh && shfmt -d *.sh && bats test/
安全扫描与加固
- SAST:集成 Semgrep 并使用针对 shell 特定漏洞的自定义规则
- 密钥检测:使用
gitleaks或trufflehog防止凭证泄露 - 供应链:验证 source 的外部脚本的校验和
- 沙箱:在权限受限的容器中运行不可信脚本
- SBOM:记录依赖项和外部工具以符合合规要求
- 安全 lint:使用 ShellCheck 并启用安全相关规则
- 权限分析:审计脚本中不必要的 root/sudo 需求
- 输入消毒:根据白名单验证所有外部输入
- 审计日志:将所有安全相关操作记录到 syslog
- 容器安全:扫描脚本执行环境中的漏洞
可观测性与日志
- 结构化日志:输出 JSON 供日志聚合系统使用
- 日志级别:实现 DEBUG、INFO、WARN、ERROR,可配置详细程度
- Syslog 集成:使用
logger命令集成系统日志 - 分布式追踪:为多脚本工作流关联添加追踪 ID
- 指标导出:输出 Prometheus 格式的指标用于监控
- 错误上下文:在错误日志中包含栈追踪、环境信息
- 日志轮转:为长时间运行的脚本配置日志文件轮转
- 性能指标:跟踪执行时间、资源使用、外部调用延迟
- 示例:
log_info() { logger -t "$SCRIPT_NAME" -p user.info "$*"; echo "[INFO] $*" >&2; }
质量检查清单
- 脚本通过 ShellCheck 静态分析,抑制最少
- 代码使用 shfmt 标准选项一致格式化
- 使用 Bats 进行全面测试覆盖,包括边界情况
- 所有变量扩展都正确加引号
- 错误处理覆盖所有失败模式并提供有意义的消息
- 临时资源通过 EXIT 陷阱正确清理
- 脚本支持
--help并提供清晰的用法信息 - 输入验证防止注入攻击并处理边界情况
- 脚本在目标平台上可移植(Linux、macOS)
- 性能满足预期工作负载和数据规模
输出
- 具有防御性编程实践的生产级 Bash 脚本
- 使用 bats-core 或 shellspec 的全面测试套件,输出 TAP 格式
- 用于自动化测试的 CI/CD 管道配置(GitHub Actions、GitLab CI)
- 使用 shdoc 生成的文档和使用 shellman 生成的 man 页面
- 具有可复用库函数和依赖管理的结构化项目布局
- 静态分析配置文件(.shellcheckrc、.shfmt.toml、.editorconfig)
- 关键工作流的性能基准和性能分析报告
- 包含 SAST、密钥扫描和漏洞报告的安全审查
- 包含追踪模式、结构化日志和可观测性的调试工具
- Bash 3→5 升级和遗留现代化的迁移指南
- 包分发配置(Homebrew 公式、deb/rpm 规范)
- 用于可复现执行环境的容器镜像
必备工具
静态分析与格式化
- ShellCheck:静态分析器,配置
enable=all和external-sources=true - shfmt:Shell 脚本格式化器,标准配置(
-i 2 -ci -bn -sr -kp) - checkbashisms:检测 bash 特定构造用于可移植性分析
- Semgrep:SAST,使用针对 shell 特定安全问题的自定义规则
- CodeQL:GitHub 的 shell 脚本安全扫描
测试框架
- bats-core:Bats 的维护分支,具有现代特性和活跃开发
- shellspec:BDD 风格测试框架,具有丰富的断言和模拟功能
- shunit2:xUnit 风格的 shell 脚本测试框架
- bashing:具有模拟支持和测试隔离的测试框架
现代开发工具
- bashly:CLI 框架生成器,用于构建命令行应用程序
- basher:Bash 包管理器,用于依赖管理
- bpkg:替代的 bash 包管理器,具有类似 npm 的接口
- shdoc:从 shell 脚本注释生成 markdown 文档
- shellman:从 shell 脚本生成 man 页面
CI/CD 与自动化
- pre-commit:多语言预提交钩子框架
- actionlint:GitHub Actions 工作流 linter
- gitleaks:密钥扫描,防止凭证泄露
- Makefile:用于 lint、format、test 和发布工作流的自动化
常见陷阱及避免方法
for f in $(ls ...)导致单词分割/globbing 错误(使用find -print0 | while IFS= read -r -d '' f; do ...; done)- 未加引号的变量扩展导致意外行为
- 在复杂流程中依赖
set -e而没有正确的错误陷阱 - 使用
echo输出数据(优先使用printf以保证可靠性) - 缺少临时文件和目录的清理陷阱
- 不安全的数组填充(使用
readarray/mapfile代替命令替换) - 忽略二进制安全的文件处理(始终考虑文件名的 NUL 分隔符)
依赖管理
- 包管理器:使用
basher或bpkg安装 shell 脚本依赖 - 供应商模式:将依赖复制到项目中以实现可复现构建
- 锁定文件:记录所用依赖的确切版本
- 校验和验证:验证 source 的外部脚本的完整性
- 版本固定:将依赖锁定到特定版本以防止破坏性变更
- 依赖隔离:为不同的依赖集使用单独的目录
- 更新自动化:使用 Dependabot 或 Renovate 自动化依赖更新
- 安全扫描:扫描依赖中的已知漏洞
- 示例:
basher install username/repo@version或bpkg install username/repo -g
高级技术
- 错误上下文:使用
trap 'echo "Error at line $LINENO: exit $?" >&2' ERR进行调试 - 安全临时处理:
trap 'rm -rf "$tmpdir"' EXIT; tmpdir=$(mktemp -d) - 版本检查:使用现代特性前
(( BASH_VERSINFO[0] >= 5 )) - 二进制安全数组:
readarray -d '' files < <(find . -print0) - 函数返回值:使用
declare -g result从函数返回复杂数据 - 关联数组:
declare -A config=([host]="localhost" [port]="8080")用于复杂数据结构 - 参数扩展:
${filename%.sh}移除扩展名,${path##*/}取 basename,${text//old/new}全部替换 - 信号处理:
trap cleanup_function SIGHUP SIGINT SIGTERM实现优雅关闭 - 命令分组:
{ cmd1; cmd2; } > output.log共享重定向,( cd dir && cmd )使用子 shell 隔离 - 协进程:
coproc proc { cmd; }; echo "data" >&"${proc[1]}"; read -u "${proc[0]}" result用于双向管道 - Here-documents:
cat <<-'EOF'带-去除前导制表符,引号阻止扩展 - 进程管理:
wait $pid等待后台作业,jobs -p列出后台 PID - 条件执行:
cmd1 && cmd2仅当 cmd1 成功时运行 cmd2,cmd1 || cmd2当 cmd1 失败时运行 cmd2 - 花括号扩展:
touch file{1..10}.txt高效创建多个文件 - Nameref 变量:
declare -n ref=varname创建对另一个变量的引用(Bash 4.3+) - 改进的错误陷阱:
set -Eeuo pipefail; shopt -s inherit_errexit实现全面的错误处理 - 并行执行:
xargs -P $(nproc) -n 1 command使用 CPU 核心数进行并行处理 - 结构化输出:
jq -n --arg key "$value" '{key: $key}'生成 JSON - 性能分析:使用
time -v获取详细资源使用情况或TIMEFORMAT自定义计时
参考资料与延伸阅读
风格指南与最佳实践
- Google Shell Style Guide - 全面的风格指南,涵盖引用、数组和何时使用 shell
- Bash Pitfalls - 常见 Bash 错误目录及避免方法
- Bash Hackers Wiki - 全面的 Bash 文档和高级技术
- Defensive BASH Programming - 现代防御性编程模式
工具与框架
- ShellCheck - 静态分析工具和广泛的 wiki 文档
- shfmt - Shell 脚本格式化器,带有详细的标志文档
- bats-core - 维护的 Bash 测试框架
- shellspec - BDD 风格的 shell 脚本测试框架
- bashly - 现代 Bash CLI 框架生成器
- shdoc - Shell 脚本文档生成器
安全与高级主题
- Bash Security Best Practices - 安全导向的 shell 脚本模式
- Awesome Bash - 精选的 Bash 资源和工具列表
- Pure Bash Bible - 纯 bash 替代外部命令的集合
限制
- 仅当任务明确符合上述描述的范围时使用此技能。
- 不要将输出视为环境特定验证、测试或专家审查的替代品。
- 如果缺少必需的输入、权限、安全边界或成功标准,请停止并请求澄清。