SuperAPI AI 工具集

linux-bash-scripting

编程与开发

查看项目

更新于 10-15 17:13

技能介绍

用途

本技能用于编写 Linux 上的防御式 Bash 脚本,目标是产出能通过 shellcheck --enable=all 和 shfmt -d 且零警告的脚本。适用目标环境为 Linux 上的 GNU Bash 4.4+,不包含 macOS/BSD 兼容写法、Windows 路径,也不做仅 POSIX 的限制。

脚本基础

推荐的基础骨架:

#!/usr/bin/env bash
set -Eeuo pipefail
shopt -s inherit_errexit

SCRIPT_DIR=$(dirname -- "${BASH_SOURCE[0]}") || exit "$?"
SCRIPT_DIR=$(cd -- "${SCRIPT_DIR}" && pwd -P) || exit "$?"
readonly SCRIPT_DIR

trap 'printf "Error at %s:%d\n" "${BASH_SOURCE[0]}" "$LINENO" >&2' ERR
trap 'rm -rf -- "${_tmpdir:-}"' EXIT

要点:

  • -E 让 ERR trap 传播进函数。
  • inherit_errexit 让 errexit 传播进 $() 命令替换。
  • 脚本自身的数据文件应相对 SCRIPT_DIR 解析,绝不使用调用者的 cwd 或 git rev-parse --show-toplevel。共享 linter 从其他项目的 git hook、cron 任务或包装脚本调用时,cwd 属于别人,调用者相对的规则路径会解析到不存在的文件:规则集加载为空,零违规,退出 0。这是静默空操作而非报错,而在其自身仓库内运行会因错误原因通过。应在一个不属于脚本自身目录树的临时目录中演练一次。
  • 临时目录始终在 EXIT trap 中创建:_tmpdir=$(mktemp -d)。
  • 将主体包进 main 函数,并对其调用做保护,使直接执行时运行、被 source 时只定义函数以便测试。

核心规则

  • 引用展开,命令用数组,绝不把外部数据当作 shell 代码求值。
  • 算术前校验数值语法、符号与应用边界。无符号数字先以 10# 转换再施加符号;10#-08 无效。
  • 密钥不得出现在进程参数与跟踪中。通过 stdin 传入,并使用能识别 JSON 的编码器。
  • 在“不存在”与“检查失败”有区别处,检查精确退出状态。将 local 与 readonly 声明同命令替换分开。
  • 使用 NUL 分隔的文件迭代,校验必需的标志值,拒绝冲突的输出/目标组合。
  • 原子替换时在目标旁暂存;多文件激活时切换单一暂存发布引用。
  • 保留无关文件,并在清理后报告实际的信号或命令状态。
  • 不要假设 Bash 选项在 sh 中可用、GNU 工具模式等同于系统调用模式,或配置的回退路径一定可用。

任务专用参考

实现对应行为前先阅读相关参考:

  • 命令执行、外部输入、数值转换、参数解析、迭代或子进程状态处理:input-and-process-safety.md。
  • 文件激活、密钥、加锁、重试、清理、权限、日志或可重启自动化:production-patterns.md。

性能

  • 用参数展开替代外部命令:${path%/*} 而非 dirname,${path##*/} 而非 basename,${var//old/new} 而非 sed。
  • 用 (( )) 替代 expr;用 [[ =~ ]] 替代 echo | grep。
  • 缓存结果:val=$(cmd) 一次,复用 $val。
  • 并行工作用 xargs -0 -P "$(nproc)"。
  • 用 declare -A map 做查找,替代反复 grep。

Bash 4.4+ / 5.x

  • ${var@Q} 输出 shell 引用形式(Bash 4.4+);${var@U} 大写、${var@L} 小写(Bash 5.1+)。
  • declare -n ref=varname 命名引用用于间接访问。
  • wait -n 等待任一后台任务。
  • $EPOCHSECONDS、$EPOCHREALTIME:无需 fork date 即可获得时间戳(Bash 5.0+)。

Linux 特有

  • GNU coreutils 与 macOS 不同:sed -i(无 '' 后缀)、grep -P(支持 PCRE)、readlink -f(规范路径)。
  • timeout 30s cmd 防止自动化挂起。
  • sed -i 会把符号链接替换为普通文件:它写入临时文件并重命名覆盖该路径,两份副本之后会无报错地分叉。应使用 sed -i --follow-symlinks 或按名称编辑目标,并在脚本化编辑后检查 git status --short 中的 T(类型变更)。

ShellCheck

运行 shellcheck --enable=all script.sh。关键规则:

  • SC2155:声明与赋值分开。
  • SC2086:变量加双引号。
  • SC2046:命令替换加引号。
  • SC2164:cd dir || exit。
  • SC2327/SC2328:保持 stdout 对命令替换可用;将其重定向走会使捕获值为空。
  • 从 ${BASH_REMATCH[n]} 读取正则捕获,而非位置参数。

提交前:shellcheck *.sh && shfmt -i 2 -ci -d *.sh。

验证

宣布完成前运行 shellcheck --enable=all 与 shfmt -d,要求零警告。测试边界情况:空输入、缺失文件、路径含空格。

若 shellcheck 或 shfmt 未安装(command -v shellcheck 失败),则检查是被跳过而非通过:报告“static analysis not run: shellcheck unavailable”,并回退到 bash -n 仅做语法检查。被跳过的 linter 与上文空规则文件一样,是静默空操作。

限制

本技能面向 Linux 上的 GNU Bash 4.4+,不提供 macOS/BSD 兼容写法、Windows 路径或仅 POSIX 限制。其他平台、版本或工具链的适配方式暂未提供。