何时使用
适用:编写生产自动化脚本、CI/CD 流水线脚本、系统运维工具、需容错的部署脚本、必须安全处理边界情况的脚本、可维护的 shell 函数库,以及需要结构化日志/监控或跨平台运行的脚本。
不该用(负边界):
- 只需一条临时 shell 命令而非脚本时。
- 目标环境强制要求严格 POSIX
sh(本技能依赖 Bash 特性,如[[ ]]、数组、mapfile)。 - 任务与 shell 脚本/自动化无关。
- 不能替代针对具体环境的验证、测试与专家评审;缺少输入、权限、安全边界或验收标准时应先停下来澄清。
步骤
- 确认目标 shell、操作系统与执行环境(Bash 版本、是否 root、是否跨平台)。
- 脚本起始即开启严格模式与安全默认值。
- 校验入参、引用变量加引号、安全处理文件。
- 加上日志、错误 trap 与基本测试(含错误路径)。
安全约束:破坏性命令未经确认或无 dry-run 不执行;非必要不以 root 运行。
指令
核心防御原则:
- 严格模式:
set -Eeuo pipefail-E函数继承 ERR trap;-e任一命令非零即退出;-u引用未定义变量即退出;-o pipefail管道中任一环失败即失败。
- 错误陷阱与清理:用
trap ... ERR报告出错行$LINENO,用trap ... EXIT清理临时目录。 - 变量安全:一律加引号
cp "$source" "$dest";必填变量用: "${REQUIRED_VAR:?REQUIRED_VAR is not set}"。 - 数组:迭代用
"${items[@]}";读命令输出用mapfile -t lines < <(cmd)。 - 条件判断:Bash 用
[[ ]],POSIX 用[ ];判空用[[ -z "${VAR:-}" ]]。 - 命令替换用
$()而非反引号;用command -v而非which检测可执行文件;优先printf而非echo。
最佳实践速记:严格模式、变量加引号、[[ ]]、错误 trap、校验全部输入、有意义命名的函数、带时间戳的分级日志、支持 dry-run、mktemp+trap 处理临时文件、幂等设计、声明依赖与最低版本、测试错误路径。
示例
严格模式 + 错误陷阱 + 临时目录清理:
#!/bin/bash
set -Eeuo pipefail
trap 'echo "Error on line $LINENO"' ERR
trap 'rm -rf -- "$TMPDIR"' EXIT
TMPDIR=$(mktemp -d) || { echo "ERROR: Failed to create temp directory" >&2; exit 1; }
# 脚本主体
安全确定脚本目录:
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
SCRIPT_NAME="$(basename -- "${BASH_SOURCE[0]}")"
健壮的参数解析(含 usage 与必填校验):
VERBOSE=false; DRY_RUN=false; OUTPUT_FILE=""
usage() { cat <<EOF
Usage: $0 [OPTIONS]
-v, --verbose 详细输出
-o, --output FILE 输出文件路径
-h, --help 显示帮助
EOF
exit "${1:-0}"; }
while [[ $# -gt 0 ]]; do
case "$1" in
-v|--verbose) VERBOSE=true; shift ;;
-o|--output) OUTPUT_FILE="$2"; shift 2 ;;
-h|--help) usage 0 ;;
--) shift; break ;;
*) echo "ERROR: Unknown option: $1" >&2; usage 1 ;;
esac
done
[[ -n "$OUTPUT_FILE" ]] || { echo "ERROR: -o/--output is required" >&2; usage 1; }
结构化日志:
log_info() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $*" >&2; }
log_error() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2; }
log_debug() { [[ "${DEBUG:-0}" == "1" ]] && echo "[$(date +'%F %T')] DEBUG: $*" >&2 || true; }
NUL 安全遍历文件:
while IFS= read -r -d '' file; do
echo "Processing: $file"
done < <(find "$input_dir" -maxdepth 1 -type f -print0)
dry-run 包装与幂等:
DRY_RUN="${DRY_RUN:-false}"
run_cmd() {
if [[ "$DRY_RUN" == "true" ]]; then echo "[DRY RUN] Would execute: $*"; return 0; fi
"$@"
}
run_cmd cp "$source" "$dest"
ensure_directory() {
local -r dir="$1"
[[ -d "$dir" ]] && { log_info "目录已存在: $dir"; return 0; }
mkdir -p "$dir" || { log_error "创建目录失败: $dir"; return 1; }
}
依赖检查与原子写:
check_dependencies() {
local -a missing=() required=("jq" "curl" "git")
for cmd in "${required[@]}"; do command -v "$cmd" &>/dev/null || missing+=("$cmd"); done
[[ ${#missing[@]} -eq 0 ]] || { echo "ERROR: 缺少命令: ${missing[*]}" >&2; return 1; }
}
atomic_write() {
local -r target="$1"; local tmpfile
tmpfile=$(mktemp) || return 1
cat > "$tmpfile"
mv "$tmpfile" "$target" # 原子重命名
}
注意事项
- 破坏性删除用
rm -rI(GNU/BSD 兼容,删除前提示);移动前先检查目标不存在再mv。 - 后台进程编排:用
PIDS+=($!)记录 PID,并在trap cleanup SIGTERM SIGINT中kill -TERM后wait,配合kill -0 "$pid" 2>/dev/null判断存活。 - 函数命名用
handle_*/process_*/check_*/validate_*前缀,局部变量用local -r只读声明。 - 输出不能替代环境特定的验证与测试;缺少必要输入、权限或安全边界时停下来确认。
- 参考资料:Bash 严格模式(redsymbol.net)、Google Shell 风格指南(google.github.io/styleguide/shellguide.html)。
互见
- 同主题另一来源:
.vendor/wshobson__agents/plugins/shell-scripting/skills/bash-defensive-patterns/(可对照补充)。 - 配套排错/校验类技能:verify、code-review。
采编自 sickn33/antigravity-awesome-skills(MIT 许可)。