关于 bash-pro
bash-pro 是用于编写和审查 Bash 自动化、CI/CD 与运维脚本的技能,侧重防御式错误处理、安全输入处理、可移植性以及使用 Bats 和 ShellCheck 测试。
主要功能
- 01严格错误处理与陷阱清理
- 02安全解析参数和校验输入
- 03可移植的文件与进程操作
- 04使用 Bats 测试和 ShellCheck 检查
- 05日志与脚本文档规范
适用场景
- 01审查脚本中的不安全写法
- 02构建 CI/CD Bash 自动化
- 03加固会修改文件的脚本
- 04在目标平台间测试脚本
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill bash-pro安装后在 Claude Code 中输入/bash-pro确认已加载。
详细介绍
适用场景
- 编写或审查用于自动化、CI/CD 或运维的 Bash 脚本
- 加固 Shell 脚本,以提升安全性和可移植性
不适用场景
- 需要仅使用 POSIX Shell,不能使用 Bash 特性
- 任务需要使用更高级的语言来处理复杂逻辑
- 需要使用 Windows 原生脚本(PowerShell)
指南
- 定义脚本的输入、输出和失败模式。
- 启用严格模式,并安全地解析参数。
- 使用防御性模式实现核心逻辑。
- 使用 Bats 和 ShellCheck 添加测试和 lint 检查。
安全性
- 将输入视为不可信内容;避免使用
eval和不安全的通配符。 - 执行破坏性操作前,优先使用试运行模式。
重点领域
- 采用严格错误处理进行防御式编程
- 符合 POSIX 标准并支持跨平台移植
- 安全地解析参数并验证输入
- 可靠地处理文件操作和临时资源
- 保障进程编排和管道安全
- 提供适用于生产环境的日志记录和错误报告
- 使用 Bats 框架进行全面测试
- 使用 ShellCheck 进行静态分析,并使用 shfmt 格式化代码
- 遵循现代 Bash 5.x 特性和最佳实践
- 集成 CI/CD 和自动化工作流
方法
- 始终使用严格模式
set -Eeuo pipefail,并正确设置错误捕获 - 为所有变量展开加上引号,避免单词拆分和通配符匹配问题
- 优先使用数组和恰当的迭代方式,避免使用
for f in $(ls)等不安全模式 - Bash 条件判断使用
[[ ]];需要符合 POSIX 标准时改用[ ] - 使用
getopts和 usage 函数全面解析参数 - 使用
mktemp安全地创建临时文件和目录,并通过清理陷阱进行清理 - 优先使用
printf而非echo,以确保输出格式可预测 - 使用命令替换
$(),而非反引号,以提高可读性 - 使用时间戳和可配置的详细程度实现结构化日志记录
- 设计幂等脚本,并支持试运行模式
- 使用
shopt -s inherit_errexit,以便在 Bash 4.4+ 中更好地传播错误 - 使用
IFS=$'\n\t',避免空格导致意外的单词拆分 - 使用
: "${VAR:?message}"验证必需的环境变量 - 使用
--结束选项解析,并使用rm -rf -- "$dir"执行安全操作 - 仅允许在已脱敏的非机密路径中使用
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 bash作为 shebang,以便在不同系统间移植 - 在脚本启动时检查 Bash 版本:对于 Bash 4.4+ 特性,使用
(( BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 4) )) - 验证所需的外部命令是否存在:
command -v jq &>/dev/null || exit 1 - 检测平台差异:
case "$(uname -s)" in Linux*) ... ;; Darwin*) ... ;; esac - 处理 GNU 与 BSD 工具的差异(例如,
sed -i与sed -i '') - 在所有目标平台(Linux、macOS、BSD 变体)上测试脚本
- 在脚本头部注释中说明最低版本要求
- 为平台特有功能提供回退实现
- 为提升可移植性,尽可能优先使用 Bash 内置功能,而非外部命令
- 需要符合 POSIX 标准时,避免使用 Bash 特有语法;若使用 Bash 特有功能,则应说明
可读性与可维护性
- 在脚本中使用完整形式的选项,提升清晰度:使用
--verbose而不是-v - 使用一致的命名方式:函数和变量使用 snake_case,常量使用 UPPER_CASE
- 添加注释块形式的分节标题,整理相关函数
- 函数长度控制在 50 行以内;将较大的函数重构为更小的组件
- 将相关函数归组,并添加描述性分节标题
- 使用能说明用途的描述性函数名:使用
validate_input_file,而不是check_file - 为不明显的逻辑添加行内注释,避免赘述显而易见的内容
- 保持缩进一致(使用 2 个或 4 个空格,绝不混用制表符和空格)
- 为保持一致,将左花括号放在同一行:
function_name() { - 在函数内使用空行分隔逻辑区块
- 在头部注释中说明函数参数和返回值
- 将魔法数字和字符串提取为脚本顶部的命名常量
安全模式
- 使用
readonly声明常量,避免意外修改 - 所有函数变量都使用
local关键字,避免污染全局作用域 - 为外部命令设置
timeout:curl --connect-timeout 5 --max-time 30 ...可限制网络调用时长;macOS 不自带 GNU timeout - 操作前验证文件权限:
[[ -r "$file" ]] || exit 1 - 进程替换可能会隐藏生产者的失败状态,使其无法被 pipefail 检测到;对于关键输入,应显式等待并检查其状态
- 在命令或文件操作中使用用户输入前,先对其进行清理
- 使用模式匹配验证数字输入:
[[ $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 手册逐项确认所需特性,不要假设所有 Bash 5.x 版本的转换、
shopt选项和重定向行为都相同。 EPOCHREALTIME以微秒精度报告纪元秒数;它是挂钟时间,不是单调基准计时器。- 使用现代特性前先检查版本:
(( BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 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 钩子:在
.pre-commit-config.yaml中配置shellcheck、shfmt、checkbashisms - 矩阵测试:在 Linux 和 macOS 上使用 Bash 4.4、5.0、5.1、5.2 进行测试
- 容器测试:使用官方
bash:5.2Docker 镜像进行可复现的测试 - CodeQL:在适用的情况下使用受支持语言的分析;它不是通用的 Bash 分析器
- Actionlint:验证使用 shell 脚本的 GitHub Actions 工作流文件
- 自动发布:自动标记版本并生成变更日志
- 覆盖率报告:跟踪测试覆盖率,并在出现回归时使构建失败
- 示例工作流:
shellcheck *.sh && shfmt -d *.sh && bats test/
安全扫描与加固
- SAST:集成 Semgrep,并为 shell 特有漏洞编写自定义规则
- 密钥检测:使用
gitleaks或trufflehog,防止凭据泄露 - 供应链:验证所引入外部脚本的校验和
- 沙箱:先将不可信脚本作为数据检查;仅使用容器并不意味着获准执行脚本,也不能隔离网络、挂载或密钥
- 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:分析受支持的语言和 Actions;对于独立 Bash,使用 ShellCheck
测试框架
- bats-core:Bats 的维护分支,具备现代功能并积极开发
- shellspec:BDD 风格测试框架,提供丰富的断言和模拟功能
- shunit2:用于 Shell 脚本的 xUnit 风格测试框架
- bashing:支持模拟和测试隔离的测试框架
现代开发工具
- bashly:用于构建命令行应用的 CLI 框架生成器
- basher:用于依赖管理的 Bash 包管理器
- bpkg:采用类似 npm 的接口的 Bash 包管理器替代方案
- shdoc:根据 Shell 脚本注释生成 Markdown 文档
- shellman:根据 Shell 脚本生成 man 页面
CI/CD 与自动化
- pre-commit:多语言 pre-commit 钩子框架
- actionlint:GitHub Actions 工作流检查工具
- gitleaks:密钥扫描工具,用于防止凭据泄露
- Makefile:用于 lint、格式化、测试和发布工作流的自动化工具
应避免的常见陷阱
for f in $(ls ...)会导致单词拆分和通配符匹配错误(请改用find -print0 | while IFS= read -r -d '' f; do ...; done)- 未给变量展开加引号,导致意外行为
- 在复杂流程中依赖
set -e,却没有适当的错误捕获 - 使用
echo输出数据(为确保可靠性,优先使用printf) - 未为临时文件和目录设置清理陷阱
- 不安全地填充数组(请使用
readarray/mapfile,而不是命令替换) - 忽视二进制安全的文件处理(处理文件名时务必考虑使用 NUL 分隔符)
依赖管理
- 包管理器:使用
basher或bpkg安装 Shell 脚本依赖 - 供应商化:将依赖复制到项目中,以实现可复现构建
- 锁定文件:记录所用依赖的确切版本
- 校验和验证:验证引入的外部脚本的完整性
- 版本固定:将依赖锁定到特定版本,以避免破坏性变更
- 依赖隔离:为不同的依赖集合使用单独的目录
- 更新自动化:使用 Dependabot 或 Renovate 自动更新依赖
- 安全扫描:扫描依赖中已知的漏洞
- 示例:
basher install username/repo@version或bpkg install username/repo -g
高级技巧
- 错误上下文:使用
trap 'echo "Error at line $LINENO: exit $?" >&2' ERR进行调试 - 安全处理临时文件:
tmpdir=$(mktemp -d) || exit 1; trap 'rm -rf -- "$tmpdir"' EXIT - 版本检查:使用现代功能前,先运行
(( BASH_VERSINFO[0] >= 5 )) - 二进制安全数组:
readarray -d '' files < <(find . -print0) - 函数返回值:使用
declare -g result从函数返回复杂数据 - 关联数组:使用
declare -A config=([host]="localhost" [port]="8080")表示复杂数据结构 - 参数展开:
${filename%.sh}移除扩展名,${path##*/}获取基本文件名,${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 文档:
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 风格指南 - 全面的风格指南,涵盖引号、数组以及何时使用 Shell
- Bash 常见陷阱 - 常见 Bash 错误及其避免方法汇总
- Bash Hackers Wiki - 全面的 Bash 文档与高级技巧
- 防御式 BASH 编程 - 现代防御式编程模式
工具与框架
- ShellCheck - 静态分析工具,附有详尽的 Wiki 文档
- shfmt - Shell 脚本格式化工具,附有详细的标志说明
- bats-core - 持续维护的 Bash 测试框架
- shellspec - 面向 Shell 脚本的 BDD 风格测试框架
- bashly - 现代 Bash CLI 框架生成器
- shdoc - Shell 脚本文档生成器
安全与高级主题
- Bash 安全最佳实践 - 注重安全性的 Shell 脚本模式
- Awesome Bash - 精选的 Bash 资源与工具列表
- Pure Bash Bible - 纯 Bash 实现的外部命令替代方案合集
示例
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
task_tmpdir=$(mktemp -d) || exit 1
trap 'rm -rf -- "$task_tmpdir"' EXIT
printf '%s\n' 'example' > "$task_tmpdir/input.txt"
[[ -s "$task_tmpdir/input.txt" ]] || exit 1
目录会在安装清理逻辑之前创建。删除前,请对已批准的路径进行引用或验证:-- 会停止选项解析,但无法避免误删错误的绝对路径。严格模式和 ERR 陷阱也无法捕获所有上下文中的失败。
限制
- 仅当任务明确符合上述范围时,才使用此技能。
- 不要将输出视为针对特定环境进行验证、测试或专家审查的替代方案。
- 如果缺少必要的输入、权限、安全边界或成功标准,请停止并请求澄清。
主要支持的语言:CodeQL 支持的语言。
评论
支持 Markdown