- AI 技能
- AI 插件
- 人工智能
【免费下载链接】agentic-awesome-skills
AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 2,600+ agentic skills. Includes CLI, local MCP, catalog, plugins, and Workbench.
在 agentic-awesome-skills 仓库中,从scripts/下的激活与校验脚本到各技能目录的辅助脚本,处处可见防御式 Bash 编程的实践。本指南以技能bash-defensive-patterns的实施手册为骨架,结合仓库内真实脚本作为实战佐证,系统讲解如何编写面向生产环境的健壮 Shell 脚本。读完本文,你将掌握严格模式、错误捕获、变量与文件安全、参数解析、日志与进程编排等一整套可落地的防御模式,并能直接应用于 CI/CD 流水线、系统运维工具与部署自动化脚本。
该技能的使用边界在 SKILL.md 中有明确说明:它适用于编写生产级自动化脚本、CI/CD 流水线脚本、系统管理工具、容错部署自动化,以及需要跨平台运行的脚本;而当只需要单条临时命令、目标环境仅允许严格 POSIX sh、或任务与 Shell 脚本无关时,则不适用。动手前先确认目标 Shell、操作系统与执行环境,并始终遵循安全边界:破坏性命令必须有确认或 dry-run 开关,非必要不 root 运行。
核心防御原则
1. 严格模式:脚本的第一行保险
在每个脚本开头启用严格模式,可以在错误发生的早期阶段将其暴露出来:
#!/bin/bash set -Eeuo pipefail # Exit on error, unset variables, pipe failures四个关键 flag 各司其职:
set -E:让 ERR trap 在函数内部也能被继承和触发,防止错误被函数作用域吞掉;set -e:任一命令返回非零即退出,避免“错误被忽略、脚本继续执行”的连锁失败;set -u:引用未定义变量即退出,提前发现拼写错误或环境变量缺失;set -o pipefail:管道中任一命令失败即整体失败,而非只看最后一个命令的退出码。
仓库中的真实脚本是这一原则的直接印证。scripts/validate-glossary.sh 与 scripts/validate-links.sh 都在首行#!/bin/bash之后立即启用set -euo pipefail;scripts/activate-skills.sh 使用#!/usr/bin/env bash以便在PATH中查找 bash,同样紧跟set -euo pipefail。技能目录下如 skills/git-pushing/scripts/smart_commit.sh、skills/wiki-builder/scripts/init_wiki.sh 等大量辅助脚本也采用了相同的严格模式,说明这是仓库内约定俗成的基线。
注意:set -e并非万能。对于你“预期可能失败”的命令(如grep无匹配、command -v未命中),应显式处理,例如在 if 条件中使用、追加|| true,或用if ! cmd; then ...; fi的形式,避免严格模式误伤预期失败路径。
2. 错误捕获与清理:保证资源不泄漏
实现退出或出错时的清理逻辑,是脚本可靠性的第二道防线:
#!/bin/bash set -Eeuo pipefail trap 'echo "Error on line $LINENO"' ERR trap 'echo "Cleaning up..."; rm -rf "$TMPDIR"' EXIT TMPDIR=$(mktemp -d) # Script code heretrap ... ERR在任意命令失败时打印出错行号,配合$LINENO可以快速定位故障点;trap ... EXIT保证无论正常结束还是异常退出都会执行清理,rm -rf "$TMPDIR"回收临时目录。
仓库中的 scripts/activate-skills.sh 是标准范例:脚本用mktemp "${TMPDIR:-/tmp}/ag-skills.XXXXXX"创建临时文件记录技能清单,随后定义cleanup() { rm -f "$SKILLS_LIST_FILE"; }并注册trap cleanup EXIT,确保任何退出路径都不会残留临时文件。在后续小节中你还会看到用trap配合信号处理来编排后台进程的模式。
3. 变量安全:引号是免费的安全网
不加引号的变量会被 Bash 分词(word splitting)和路径展开(globbing)影响,文件名含空格或*时极易出事故:
# Wrong - unsafe cp $source $dest # Correct - safe cp "$source" "$dest" # Required variables - fail with message if unset : "${REQUIRED_VAR:?REQUIRED_VAR is not set}"- 双引号包裹
"$variable"是最基础也是最重要的防御手段; : "${REQUIRED_VAR:?REQUIRED_VAR is not set}"是“必填变量”惯用法:变量未设置时立即以指定错误信息退出,适合校验部署环境变量、外部传入参数;- 附带一提,
${VAR:-default}与${VAR:=default}分别用于“未设置时取默认值(不改写)”和“未设置时赋默认值(改写)”,scripts/activate-skills.sh 中大量使用了DEFAULT_BASE_DIR="${HOME:-$PWD}/.agents"、BASE_DIR="${AG_BASE_DIR:-$DEFAULT_BASE_DIR}"这类形式,通过环境变量覆盖默认值,兼顾可配置性与安全性。
4. 数组处理:复杂数据的安全容器
数组避免了对字符串做IFS分隔的脆弱写法,是处理批量数据的推荐方式:
# Safe array iteration declare -a items=("item 1" "item 2" "item 3") for item in "${items[@]}"; do echo "Processing: $item" done # Reading output into array safely mapfile -t lines < <(some_command) readarray -t numbers < <(seq 1 10)"${items[@]}"逐个展开元素(区别于"${items[*]}"的整体合并),即使元素含空格也安全;mapfile/readarray -t将命令输出按行读入数组,-t去掉行尾换行符。
5. 条件判断安全:[[ ]]优先,[ ]备选
# Bash - safer if [[ -f "$file" && -r "$file" ]]; then content=$(<"$file") fi # POSIX - portable if [ -f "$file" ] && [ -r "$file" ]; then content=$(cat "$file") fi # Test for existence before operations if [[ -z "${VAR:-}" ]]; then echo "VAR is not set or is empty" fi- Bash 的
[[ ]]内支持&&、||、正则匹配、模式匹配,且不做分词与通配展开,比[ ]更稳健; - 需要跨平台或严格 POSIX 环境时退化为
[ ],此时&&必须在[ ]之外使用; [[ -z "${VAR:-}" ]]同时覆盖“未设置”和“空字符串”两种情况,是判空的标准写法。
基础防御模式
模式 1:脚本目录安全探测
$0在通过PATH调用、符号链接或source执行时不可靠,应基于${BASH_SOURCE[0]}计算:
#!/bin/bash set -Eeuo pipefail # Correctly determine script directory SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" SCRIPT_NAME="$(basename -- "${BASH_SOURCE[0]}")" echo "Script location: $SCRIPT_DIR/$SCRIPT_NAME"--防止目录名以-开头被当成选项;pwd -P解析符号链接得到物理路径。
仓库脚本 scripts/activate-skills.sh 和 scripts/validate-links.sh 均采用此模式定位自身,并进一步以SCRIPT_DIR推导PROJECT_ROOT与REPO_SKILLS_DIR,实现“脚本放哪儿都能跑”的相对路径定位。
模式 2:带文档与错误处理的函数模板
#!/bin/bash set -Eeuo pipefail # Prefix for functions: handle_*, process_*, check_*, validate_* # Include documentation and error handling validate_file() { local -r file="$1" local -r message="${2:-File not found: $file}" if [[ ! -f "$file" ]]; then echo "ERROR: $message" >&2 return 1 fi return 0 } process_files() { local -r input_dir="$1" local -r output_dir="$2" # Validate inputs [[ -d "$input_dir" ]] || { echo "ERROR: input_dir not a directory" >&2; return 1; } # Create output directory if needed mkdir -p "$output_dir" || { echo "ERROR: Cannot create output_dir" >&2; return 1; } # Process files safely while IFS= read -r -d '' file; do echo "Processing: $file" # Do work done < <(find "$input_dir" -maxdepth 1 -type f -print0) return 0 }要点:
- 函数名使用
handle_*、process_*、check_*、validate_*等语义前缀,可读性高; local -r声明只读局部变量,避免污染全局;- 每个可能失败的步骤都校验前置条件并输出到 stderr(
>&2),返回非零退出码; while IFS= read -r -d ''搭配find ... -print0是 NUL 分隔的安全迭代,能正确处理文件名中的空格、换行甚至特殊字符。
模式 3:临时文件的安全处理
#!/bin/bash set -Eeuo pipefail trap 'rm -rf -- "$TMPDIR"' EXIT # Create temporary directory TMPDIR=$(mktemp -d) || { echo "ERROR: Failed to create temp directory" >&2; exit 1; } # Create temporary files in directory TMPFILE1="$TMPDIR/temp1.txt" TMPFILE2="$TMPDIR/temp2.txt" # Use temporary files touch "$TMPFILE1" "$TMPFILE2" echo "Temp files created in: $TMPDIR"mktemp -d创建唯一且权限受限的临时目录,避免在/tmp下硬编码文件名造成冲突或安全风险;trap 'rm -rf -- "$TMPDIR"' EXIT保证清理;rm -rf --中的--防止目录名被误解析为选项。
前文提到的 scripts/activate-skills.sh 正是该模式的仓库实例。
模式 4:健壮的参数解析
#!/bin/bash set -Eeuo pipefail # Default values VERBOSE=false DRY_RUN=false OUTPUT_FILE="" THREADS=4 usage() { cat <<EOF Usage: $0 [OPTIONS] Options: -v, --verbose Enable verbose output -d, --dry-run Run without making changes -o, --output FILE Output file path -j, --jobs NUM Number of parallel jobs -h, --help Show this help message EOF exit "${1:-0}" } # Parse arguments while [[ $# -gt 0 ]]; do case "$1" in -v|--verbose) VERBOSE=true shift ;; -d|--dry-run) DRY_RUN=true shift ;; -o|--output) OUTPUT_FILE="$2" shift 2 ;; -j|--jobs) THREADS="$2" shift 2 ;; -h|--help) usage 0 ;; --) shift break ;; *) echo "ERROR: Unknown option: $1" >&2 usage 1 ;; esac done # Validate required arguments [[ -n "$OUTPUT_FILE" ]] || { echo "ERROR: -o/--output is required" >&2; usage 1; }模式要点:
- 先声明默认值,再解析覆盖,最后校验必填项;
- 同时支持短选项(
-v)与长选项(--verbose),用--显式结束选项解析; - 未知选项报错并输出 usage 后以非零状态退出;
- 选项带参时(如
-o、-j)用shift 2消耗两个位置参数; usage() { exit "${1:-0}"; }让 usage 既可用于帮助(退出码 0)也可用于报错(退出码 1)。
模式 5:结构化日志
#!/bin/bash set -Eeuo pipefail # Logging functions log_info() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $*" >&2 } log_warn() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] WARN: $*" >&2 } log_error() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2 } log_debug() { if [[ "${DEBUG:-0}" == "1" ]]; then echo "[$(date +'%Y-%m-%d %H:%M:%S')] DEBUG: $*" >&2 fi } # Usage log_info "Starting script" log_debug "Debug information" log_warn "Warning message" log_error "Error occurred"- 统一的时间戳 + 级别格式便于 grep 与后续解析;
- 日志输出到 stderr(
>&2)避免污染 stdout 管道数据; log_debug由环境变量DEBUG=1控制开关,默认关闭。
模式 6:带信号处理的进程编排
#!/bin/bash set -Eeuo pipefail # Track background processes PIDS=() cleanup() { log_info "Shutting down..." # Terminate all background processes for pid in "${PIDS[@]}"; do if kill -0 "$pid" 2>/dev/null; then kill -TERM "$pid" 2>/dev/null || true fi done # Wait for graceful shutdown for pid in "${PIDS[@]}"; do wait "$pid" 2>/dev/null || true done } trap cleanup SIGTERM SIGINT # Start background tasks background_task & PIDS+=($!) another_task & PIDS+=($!) # Wait for all background processes wait- 用
$!记录每个后台任务的 PID 到数组; kill -0 "$pid"先探测进程是否存活,避免向不存在的 PID 发送信号;- 收到
SIGTERM/SIGINT时逐个kill -TERM,再wait等待优雅退出; || true显式容忍“进程已退出”这类预期内的失败,防止触发严格模式退出。
模式 7:安全的文件操作
#!/bin/bash set -Eeuo pipefail # Use -i flag to move safely without overwriting safe_move() { local -r source="$1" local -r dest="$2" if [[ ! -e "$source" ]]; then echo "ERROR: Source does not exist: $source" >&2 return 1 fi if [[ -e "$dest" ]]; then echo "ERROR: Destination already exists: $dest" >&2 return 1 fi mv "$source" "$dest" } # Safe directory cleanup safe_rmdir() { local -r dir="$1" if [[ ! -d "$dir" ]]; then echo "ERROR: Not a directory: $dir" >&2 return 1 fi # Use -I flag to prompt before rm (BSD/GNU compatible) rm -rI -- "$dir" } # Atomic file writes atomic_write() { local -r target="$1" local -r tmpfile tmpfile=$(mktemp) || return 1 # Write to temp file first cat > "$tmpfile" # Atomic rename mv "$tmpfile" "$target" }三个子模式的共同思路都是“先校验,再操作”:
safe_move检查源存在、目标不存在,杜绝覆盖;safe_rmdir只接受目录,且用rm -rI交互式确认(BSD/GNU 兼容);atomic_write先写临时文件再mv原子改名,保证目标文件不会出现“写了一半”的中间状态——读者若观察validate-glossary.sh中反复以{ ... } >> "$OUTPUT_FILE"追加报告片段,再最后一次性落盘的做法,会看到同一“原子性优先”的思路。
模式 8:幂等脚本设计
#!/bin/bash set -Eeuo pipefail # Check if resource already exists ensure_directory() { local -r dir="$1" if [[ -d "$dir" ]]; then log_info "Directory already exists: $dir" return 0 fi mkdir -p "$dir" || { log_error "Failed to create directory: $dir" return 1 } log_info "Created directory: $dir" } # Ensure configuration state ensure_config() { local -r config_file="$1" local -r default_value="$2" if [[ ! -f "$config_file" ]]; then echo "$default_value" > "$config_file" log_info "Created config: $config_file" fi } # Rerunning script multiple times should be safe ensure_directory "/var/cache/myapp" ensure_config "/etc/myapp/config" "DEBUG=false"幂等的核心是“先查询状态、再决定动作”:资源已存在就直接返回成功,不存在才创建。这让脚本可以安全地重复执行(如 CI 重跑、定时任务重放),而不会产生累积副作用。scripts/activate-skills.sh 中 “already active”/“restoring” 的分支判断,以及 skills/git-pushing/scripts/smart_commit.sh 这类提交辅助脚本对“重复执行安全”的处理,都是幂等设计的现实体现。
模式 9:安全的命令替换
#!/bin/bash set -Eeuo pipefail # Use $() instead of backticks name=$(<"$file") # Modern, safe variable assignment from file output=$(command -v python3) # Get command location safely # Handle command substitution with error checking result=$(command -v node) || { log_error "node command not found" return 1 } # For multiple lines mapfile -t lines < <(grep "pattern" "$file") # NUL-safe iteration while IFS= read -r -d '' file; do echo "Processing: $file" done < <(find /path -type f -print0)- 一律使用
$()而非反引号:$()支持嵌套、可读性更好,且反引号内的反斜杠转义极易出错; $(<"$file")直接从文件读内容,比cat少一次子进程;- 关键命令替换后用
|| { ...; }显式处理失败; - 多行输出用
mapfile -t读入数组,任意文件名用 NUL 分隔的read -r -d ''迭代。
模式 10:Dry-Run 支持
#!/bin/bash set -Eeuo pipefail DRY_RUN="${DRY_RUN:-false}" run_cmd() { if [[ "$DRY_RUN" == "true" ]]; then echo "[DRY RUN] Would execute: $*" return 0 fi "$@" } # Usage run_cmd cp "$source" "$dest" run_cmd rm "$file" run_cmd chown "$owner" "$target"run_cmd把所有可能产生副作用的命令包裹起来:dry-run 模式下只打印将要执行的命令而不真正执行。这既便于用户预览变更,也是技能 SKILL.md 中“破坏性命令必须有确认或 dry-run 开关”安全要求的直接落点。
高级防御技巧
命名参数模式
位置参数在参数增多时极易错位,函数内改用--key=value形式的命名参数可大幅提升可读性:
#!/bin/bash set -Eeuo pipefail process_data() { local input_file="" local output_dir="" local format="json" # Parse named parameters while [[ $# -gt 0 ]]; do case "$1" in --input=*) input_file="${1#*=}" ;; --output=*) output_dir="${1#*=}" ;; --format=*) format="${1#*=}" ;; *) echo "ERROR: Unknown parameter: $1" >&2 return 1 ;; esac shift done # Validate required parameters [[ -n "$input_file" ]] || { echo "ERROR: --input is required" >&2; return 1; } [[ -n "$output_dir" ]] || { echo "ERROR: --output is required" >&2; return 1; } }${1#*=}是 Bash 参数展开,用于截取--input=value中=之后的部分。函数调用变为process_data --input=x --output=dir --format=json,自文档化程度远高于裸位置参数。
依赖检查
脚本依赖外部命令时,启动时集中校验比用到时再报错更友好:
#!/bin/bash set -Eeuo pipefail check_dependencies() { local -a missing_deps=() local -a required=("jq" "curl" "git") for cmd in "${required[@]}"; do if ! command -v "$cmd" &>/dev/null; then missing_deps+=("$cmd") fi done if [[ ${#missing_deps[@]} -gt 0 ]]; then echo "ERROR: Missing required commands: ${missing_deps[*]}" >&2 return 1 fi } check_dependencies- 使用
command -v而非which:command -v是 POSIX 内置,跨平台一致,且不会因非零退出码在严格模式下造成困扰; - 一次性收集所有缺失命令再汇总报错,避免用户逐个安装。
仓库中的 scripts/validate-glossary.sh 正是该技巧的实例——脚本开头即检查jq是否安装,未安装时给出针对 Ubuntu/Debian 与 macOS 的安装提示并退出;scripts/activate-skills.sh 的resolve_python函数则用command -v python3/command -v python依次探测可用的 Python 解释器,都是command -v在真实仓库中的典型用法。
最佳实践清单速查
- 始终使用严格模式——
set -Eeuo pipefail; - 引号包裹所有变量——
"$variable"防止分词; - 优先
[[ ]]条件判断—— 比[ ]更稳健; - 实现错误捕获—— 优雅处理错误而非放任崩溃;
- 校验所有输入—— 检查文件存在性、权限与格式;
- 用函数提升复用性—— 前缀使用有意义的动词;
- 实现结构化日志—— 带时间戳与级别;
- 支持 dry-run 模式—— 允许用户预览变更;
- 安全处理临时文件—— 用
mktemp,用trap清理; - 面向幂等设计—— 脚本可安全重跑;
- 文档化需求—— 列出依赖与最低版本;
- 测试错误路径—— 确保错误处理真的生效;
- 用
command -v而非which—— 检查可执行程序更安全; - 优先
printf而非echo—— 跨系统输出行为更可预测。
综合实战:一个融合全部模式的脚本骨架
将上文模式串联起来,一个生产级脚本应具备如下骨架(可在仓库 scripts/activate-skills.sh、scripts/validate-links.sh、scripts/validate-glossary.sh 中看到对应实现):
#!/usr/bin/env bash+set -Eeuo pipefail;- 基于
${BASH_SOURCE[0]}计算SCRIPT_DIR与项目根; - 定义带
log_*前缀的结构化日志函数; - 启动时
check_dependencies校验外部命令; - 声明默认值,用
while/case解析参数,校验必填项; mktemp创建临时区,注册trap cleanup EXIT(必要时叠加SIGTERM/SIGINT);- 所有破坏性操作经
run_cmd包装,支持DRY_RUN; - 批量处理一律走 NUL 分隔的
while IFS= read -r -d ''或mapfile; - 幂等化每个
ensure_*步骤,可安全重跑。
延伸资源
- 技能入口与使用边界:plugins/agentic-awesome-skills-claude/skills/bash-defensive-patterns/SKILL.md
- 模式与模板全量参考:resources/implementation-playbook.md
- 仓库实战范例一:技能激活脚本(严格模式、脚本目录探测、mktemp+trap、NUL 迭代、幂等)—— scripts/activate-skills.sh
- 仓库实战范例二:文档链接校验脚本(严格模式、嵌入式 Python heredoc、路径归一化)—— scripts/validate-links.sh
- 仓库实战范例三:术语表一致性校验(依赖检查、输出重定向组织报告、退出码语义)—— scripts/validate-glossary.sh
- 技能库中的更多防御式脚本示例:skills/git-pushing/scripts/smart_commit.sh、skills/wiki-builder/scripts/init_wiki.sh
注意:本文所有命令与配置以当前仓库实际内容为准。在应用到具体环境前,请结合实际 Shell 版本(Bash 4+ 才支持
mapfile等特性)、目标操作系统与安全边界做验证,必要时补充测试覆盖错误路径——防御式编程的最后一公里,永远是充分的测试与人工复核。
- AI 技能
- AI 插件
- 人工智能
【免费下载链接】agentic-awesome-skills
AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 2,600+ agentic skills. Includes CLI, local MCP, catalog, plugins, and Workbench.
相关推荐
agentic-awesome-skills 之 Bash Defensive Patterns:生产级 Shell 脚本的防御式编程实战指南
agentic awesome skills 之 Bash Defensive Patterns:生产级 Shell 脚本的防御式编程实战指南 本篇指南以 ag
AI 技能AI 插件人工智能RVC 变声器 10 分钟上手:从一段音频到完整实时变声的入门指南
RVC 变声器 10 分钟上手:从一段音频到完整实时变声的入门指南 RVC(Retrieval based Voice Conversion WebUI)是一个
人工智能深度学习语音音频AI 应用微调autoskills 防御式 Bash 编程实战指南:从 strict mode 到生产级脚本的完整模式库
autoskills 防御式 Bash 编程实战指南:从 strict mode 到生产级脚本的完整模式库 导读 本文是 autoskills 技能库中 bas
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考