name: shell-scripting description: 编写健壮、可移植的 Shell 脚本,具有正确的错误处理、参数解析和测试。适用于自动化系统任务、构建 CI/CD 脚本或创建容器入口点。
Shell 脚本编写
目的
提供编写可维护 Shell 脚本的模式和最佳实践,包括错误处理、参数解析和可移植性考虑。涵盖 POSIX sh 与 Bash 的决策、参数扩展、与常见工具(jq, yq, awk)的集成,以及使用 ShellCheck 和 Bats 进行测试。
何时使用此技能
在以下情况下使用 Shell 脚本编写:
- 编排现有命令行工具和系统实用程序
- 编写 CI/CD 管道脚本(GitHub Actions, GitLab CI)
- 创建容器入口点和初始化脚本
- 自动化系统管理任务(备份、日志轮转)
- 构建开发工具(构建脚本、测试运行器)
考虑使用 Python/Go 而不是 Shell 脚本,当:
- 需要复杂的业务逻辑或数据结构
- 需要跨平台 GUI
- 大量 API 集成(REST, gRPC)
- 脚本超过 200 行且有显著的逻辑复杂性
POSIX sh vs Bash
使用 POSIX sh (#!/bin/sh) 当:
- 需要最大可移植性(Linux, macOS, BSD, Alpine)
- 需要最小化容器镜像
- 嵌入式系统或未知目标环境
使用 Bash (#!/bin/bash) 当:
- 控制环境(特定操作系统、容器)
- 需要数组或关联数组
- 高级参数扩展有益
- 进程替换
<(cmd)有用
详细比较和测试策略,参见 references/portability-guide.md。
基本错误处理
快速失败模式
#!/bin/bash
set -euo pipefail
# -e: 出错时退出
# -u: 未定义变量时退出
# -o pipefail: 管道中任何命令失败则失败
用于生产自动化、CI/CD 脚本和关键操作。
显式退出代码检查
#!/bin/bash
if ! command_that_might_fail; then
echo "错误:命令失败" >&2
exit 1
fi
用于自定义错误消息和交互式脚本。
用于清理的陷阱处理器
#!/bin/bash
set -euo pipefail
TEMP_FILE=$(mktemp)
cleanup() {
rm -f "$TEMP_FILE"
}
trap cleanup EXIT
用于确保清理临时文件、锁和资源。
综合错误模式,参见 references/error-handling.md。
参数解析
使用 getopts 的短选项(POSIX)
#!/bin/bash
while getopts "hvf:o:" opt; do
case "$opt" in
h) usage ;;
v) VERBOSE=true ;;
f) INPUT_FILE="$OPTARG" ;;
o) OUTPUT_FILE="$OPTARG" ;;
*) usage ;;
esac
done
shift $((OPTIND - 1))
长选项(手动解析)
#!/bin/bash
while [[ $# -gt 0 ]]; do
case "$1" in
--help) usage ;;
--verbose) VERBOSE=true; shift ;;
--file) INPUT_FILE="$2"; shift 2 ;;
--file=*) INPUT_FILE="${1#*=}"; shift ;;
*) break ;;
esac
done
混合方法和验证模式,参见 references/argument-parsing.md。
参数扩展快速参考
# 默认值
${var:-default} # 如果未设置则使用默认值
${var:=default} # 如果未设置则分配默认值
: "${API_KEY:?错误:必需}" # 如果未设置则报错
# 字符串操作
${#var} # 字符串长度
${var:offset:length} # 子字符串
${var%.txt} # 移除后缀
${var##*/} # 基本名称
${var/old/new} # 替换第一个
${var//old/new} # 替换全部
# 大小写转换(Bash 4+)
${var^^} # 大写
${var,,} # 小写
完整扩展模式和数组处理,参见 references/parameter-expansion.md。
常见实用程序集成
使用 jq 处理 JSON
# 提取字段
name=$(curl -sSL https://api.example.com/user | jq -r '.name')
# 过滤数组
active=$(jq '.users[] | select(.active) | .name' data.json)
# 检查存在性
if ! echo "$json" | jq -e '.field' >/dev/null; then
echo "错误:字段缺失" >&2
fi
使用 yq 处理 YAML
# 读取值(yq v4)
host=$(yq eval '.database.host' config.yaml)
# 原地更新
yq eval '.port = 5432' -i config.yaml
# 转换为 JSON
yq eval -o=json config.yaml
文本处理
# awk: 提取列
awk -F',' '{print $1, $3}' data.csv
# sed: 替换文本
sed 's/old/new/g' file.txt
# grep: 模式匹配
grep -E "ERROR|WARN" logfile.txt
详细示例和最佳实践,参见 references/common-utilities.md。
测试和验证
ShellCheck: 静态分析
# 检查脚本
shellcheck script.sh
# POSIX 合规性
shellcheck --shell=sh script.sh
# 排除警告
shellcheck --exclude=SC2086 script.sh
Bats: 自动化测试
#!/usr/bin/env bats
@test "脚本运行成功" {
run ./script.sh --help
[ "$status" -eq 0 ]
[ "${lines[0]}" = "用法:script.sh [选项]" ]
}
@test "处理缺少参数" {
run ./script.sh
[ "$status" -eq 1 ]
[[ "$output" =~ "错误" ]]
}
运行测试:
bats test/
CI/CD 集成和调试技术,参见 references/testing-guide.md。
防御性编程检查清单
#!/bin/bash
set -euo pipefail
# 检查必需命令
command -v jq >/dev/null 2>&1 || {
echo "错误:需要 jq" >&2
exit 1
}
# 检查环境变量
: "${API_KEY:?错误:需要 API_KEY}"
# 检查文件
[ -f "$CONFIG_FILE" ] || {
echo "错误:配置文件未找到:$CONFIG_FILE" >&2
exit 1
}
# 引用所有变量
echo "处理:$file" # ❌ 未引用
echo "处理:\"$file\"" # ✅ 引用
平台考虑
macOS vs Linux 差异
# sed 原地操作
sed -i '' 's/old/new/g' file.txt # macOS
sed -i 's/old/new/g' file.txt # Linux
# 可移植性:使用临时文件
sed 's/old/new/g' file.txt > file.txt.tmp
mv file.txt.tmp file.txt
# readlink
readlink -f /path # 仅 Linux
cd "$(dirname "$0")" && pwd # 可移植
完整平台差异,参见 references/portability-guide.md。
脚本类别
系统管理: Cron 任务、日志轮转、备份自动化 构建/部署: CI/CD 管道、Docker 构建、部署 开发工具: 项目设置、测试运行器、代码生成器 容器入口点: 初始化、信号处理、配置
生产脚本模板
#!/bin/bash
set -euo pipefail
readonly SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
TEMP_DIR=""
cleanup() {
local exit_code=$?
rm -rf "$TEMP_DIR"
exit "$exit_code"
}
trap cleanup EXIT
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" >&2
}
main() {
# 检查依赖项
command -v jq >/dev/null 2>&1 || exit 1
# 解析参数
# 验证输入
# 处理
# 报告结果
log "成功完成"
}
main "$@"
完整生产模板,参见 examples/production-template.sh。
工具推荐
核心工具:
- jq: JSON 解析和转换
- yq: YAML 解析(推荐 v4)
- ShellCheck: 静态分析和 linting
- Bats: 自动化测试框架
安装:
# macOS
brew install jq yq shellcheck bats-core
# Ubuntu/Debian
apt-get install jq shellcheck
相关技能
- linux-administration: 系统命令和管理
- building-ci-pipelines: 在 CI/CD 中使用脚本
- infrastructure-as-code: Terraform/Pulumi 包装器
- kubernetes-operations: kubectl 脚本、Helm 钩子
- writing-dockerfiles: 容器入口点
附加资源
参考文件:
references/error-handling.md- 综合错误模式references/argument-parsing.md- 高级解析技术references/parameter-expansion.md- 完整扩展参考references/portability-guide.md- POSIX vs Bash 差异references/testing-guide.md- ShellCheck 和 Bats 指南references/common-utilities.md- jq, yq, awk, sed 使用
示例脚本:
examples/production-template.sh- 生产就绪模板examples/getopts-basic.sh- 简单 getopts 使用examples/getopts-advanced.sh- 复杂选项处理examples/long-options.sh- 手动长选项解析examples/error-handling.sh- 错误处理模式examples/json-yaml-processing.sh- jq/yq 示例
实用脚本:
scripts/lint-script.sh- 用于 CI 的 ShellCheck 包装器scripts/test-script.sh- 用于 CI 的 Bats 包装器