1. 位置参数与特殊参数
一句话总结:
$1到$9是位置参数,$0是脚本名,$#是数量,"$@"保留每个参数原样,是参数处理的两根台柱。
位置参数是脚本接收输入的基本通道。函数内调用时,它们自动替换为函数的参数。
#!/usr/bin/env bash
# usage: ./deploy.sh <env> <app>
echo "脚本名: $0"
echo "环境: $1"
echo "应用: $2"
echo "参数个数: $#"
# 遍历所有参数,逐个打印(正确写法)
for arg in "$@"; do
echo "arg=$arg"
done
1.1 参数相关内建与变量
| 变量/内建 | 作用 |
|---|---|
$0 | 脚本或函数名 |
$1~$9 | 第 1~9 个参数 |
${10} | 第 10 个参数(两位数要花括号) |
$# | 参数个数 |
$@ / $* | 全部参数 |
shift | 左移丢弃首个参数 |
set -- | 重新设置位置参数 |
# 两位数参数必须花括号
echo "${10}"
# 空参数也要保留:用 "$@" 而不是 $*
print_args() { for a in "$@"; do printf '[%s] ' "$a"; done; echo; }
print_args "a b" c "" d # [a b] [c] [] [d]
2. 参数展开与默认值
一句话总结:
${1:-默认}为缺失参数兜底,${@:2}取参数切片,配合set --可以干净地重组参数集。
# 参数缺省:没有就给默认
env="${1:-prod}"
app="${2:-web}"
# 参数切片:从第 2 个参数开始取全部
rest=("${@:2}")
echo "剩余参数: ${rest[*]}"
# 参数必须存在:缺失直接报错退出
: "${1:?请指定环境,用法: $0 <env>}"
2.1 常用参数展开模式
| 写法 | 含义 | 例子 |
|---|---|---|
${1:-def} | 缺省用 def | 环境默认 prod |
${1:=def} | 缺省并回写 | 少用,改全局 $1 |
${1:?msg} | 缺失即报错 | 必填参数 |
${@:2} | 从第 2 个起切片 | 把前 1 个当选项 |
${!#} | 最后一个参数 | 取末位 |
# 分离"选项参数"与"普通参数"
flag="${1}"
shift
rest=("$@") # shift 之后 $@ 只剩普通参数
# 取最后一个参数
echo "${!#}"
3. getopts 选项解析
一句话总结: getopts 是内建选项解析器,按"选项字母:参数"语法循环解析
-a-b value形式,支持组合与参数缺失报错。
getopts 适合解析 -a、-b value、-abc 这类 POSIX 风格选项。带参数的选项字母后要跟冒号。
#!/usr/bin/env bash
# usage: ./app.sh [-a] [-b value] [-h]
verbose=0
batch=""
while getopts "ab:h" opt; do
case "$opt" in
a) verbose=1 ;;
b) batch="$OPTARG" ;;
h) echo "用法: $0 [-a] [-b value] [-h]"; exit 0 ;;
\?) echo "非法选项"; exit 2 ;;
esac
done
shift $((OPTIND - 1)) # 跳过已解析的选项,$@ 剩普通参数
echo "verbose=$verbose batch=$batch"
echo "剩余参数: $@"
3.1 getopts 关键点
| 要点 | 说明 |
|---|---|
optstring | 字母表,带 : 表示该选项需参数 |
$OPTARG | 当前选项的参数值 |
$OPTIND | 下一个要解析的参数下标 |
\? | 处理非法选项的分支 |
| 组合 | -ab value 等价 -a -b value |
| 终止 | 遇到 -- 停止解析 |
# 选项参数缺省:用 ${OPTARG:-默认}
while getopts "p:" opt; do
case "$opt" in
p) port="${OPTARG:-8080}" ;;
esac
done
一句话总结: getopts 最大的好处是免费拿到非法选项检测,配合
shift $((OPTIND-1))把选项与普通参数干净分离。
4. shift 的用法与模式
一句话总结: shift 把位置参数整体左移丢弃 $1,shift N 一次丢 N 个,是"消费式"解析参数的核心工具。
# shift 基础
set -- a b c
shift
echo "$1" # b
shift 2
echo "$1" # 空(已全部消费完)
# 用 shift 手动解析"子命令"
command="$1"
shift
case "$command" in
start) start_service "$@" ;; # 剩余参数交给子命令
stop) stop_service "$@" ;;
*) echo "未知命令: $command"; exit 1 ;;
esac
4.1 shift 配合模式
| 模式 | 写法 |
|---|---|
| 取首参数后丢掉 | arg="$1"; shift |
| 取选项参数 | value="$2"; shift 2 |
| 消费完所有参数 | while (( $# )); do ...; shift; done |
| 跳过选项区 | shift $((OPTIND - 1)) |
# 边消费边处理
while (( $# > 0 )); do
echo "处理: $1"
shift
done
一句话总结: shift 与 getopts 是参数解析的两套思路:getopts 管
-x选项,shift 管位置参数消费。两者组合可覆盖九成 CLI 形态。
5. IFS 与控制分隔
一句话总结: IFS 是字段分隔符,改 IFS 能自定义"分词与拼接"的规则,
read -r配合 IFS 能精确解析 CSV 之类结构。
IFS 默认是空格/制表/换行,参与词分裂、read 拆分、$* 拼接三处行为。
# read 按逗号拆分 CSV
IFS=, read -r name age city <<< "张三,25,杭州"
echo "name=$name age=$age city=$city"
# 自定义拼接:把数组拼成逗号串
arr=(a b c)
joined=$(IFS=,; echo "${arr[*]}")
echo "$joined" # a,b,c
# 解析 /etc/passwd 冒号字段
while IFS=: read -r user pass uid gid rest; do
echo "$user -> uid=$uid"
done < /etc/passwd
5.1 IFS 操作速查
| 操作 | 写法 | 结果 |
|---|---|---|
| 按逗号拆行 | IFS=, read -r a b | 拆分到变量 |
| 数组拼成串 | IFS=,; echo "${arr[*]}" | a,b,c |
| 保留整行 | IFS= read -r line | 不拆不删空格 |
| 去前导空格 | IFS=' ' read -r x | 可控 |
# 保留行首空格的读取
printf ' hello \n' | { IFS= read -r line; echo "[$line]"; }
一句话总结: IFS 配合 read 是把半结构化文本转成变量的利器,但改 IFS 是"全局副作用",最好在子 Shell
(IFS=,; ...)或单条命令里用。
6. 输入校验
一句话总结: 位置参数默认全是字符串,使用前必须校验"是不是数字"“是不是合法选项”,正则与判空是最轻量的把关。
# 判空
[[ -z "$1" ]] && { echo "缺少参数"; exit 1; }
# 数字校验
[[ "$1" =~ ^[0-9]+$ ]] || { echo "必须是数字: $1"; exit 1; }
# 枚举校验
case "$1" in dev|test|prod) ;; *) echo "非法环境"; exit 1;; esac
# 文件路径校验
[[ -f "$2" ]] || { echo "文件不存在: $2"; exit 1; }
6.1 校验函数模板
require_env() {
local env="$1"
case "$env" in
dev|test|prod) return 0 ;;
*) echo "非法环境: $env(可选 dev/test/prod)" >&2; return 1 ;;
esac
}
require_env "${1:-}" || exit 1
# 校验后设置强类型变量(全大写全局)
readonly ENV="${1:?}"
readonly APP="${2:-web}"
一句话总结: 参数校验的原则是早失败:在脚本开头把非法输入挡在门外,错误信息写明"错在哪、该给什么",比运行到一半爆炸友好得多。
7. 错误处理与退出码
一句话总结: 退出码 0 成功、1 通用错误、2 用法错误、130 被 Ctrl+C 中断;封装 error 函数与 set -e,错误路径也要可观测。
# 错误处理函数
error() {
echo "[ERROR] $(date +%F\ %T) $*" >&2
exit "${2:-1}"
}
die() { error "$@"; }
# 使用
[[ -f "$config" ]] || die "配置文件不存在: $config"
# 保留上一条命令的退出码再退出
cmd_fail; rc=$?
[ "$rc" -ne 0 ] && { echo "失败 rc=$rc" >&2; exit $rc; }
7.1 退出码约定与检查
| 退出码 | 含义 | 场景 |
|---|---|---|
| 0 | 成功 | 正常结束 |
| 1 | 通用错误 | 业务失败 |
| 2 | 用法错误 | 参数非法 |
| 130 | Ctrl+C | SIGINT 中断 |
| 127 | 命令不存在 | command not found |
# 检查命令是否存在
if ! command -v jq >/dev/null 2>&1; then
echo "缺少依赖 jq" >&2
exit 1
fi
# 忽略特定失败:临时目录清理
rm -rf "$tmp" || true
一句话总结: 错误处理三件事:错误信息进 stderr、退出码有意义、关键前置条件先行检查。做到这三点,脚本在 CI 和运维里才可诊断。
8. 总结
| 环节 | 要点 |
|---|---|
| 位置参数 | $1~$9 加花括号取高位,"$@" 保留原样 |
| 默认值 | ${1:-def} 兜底,:? 必填校验 |
| getopts | optstring 声明选项,OPTARG/OPTIND 配套 |
| shift | 左移消费参数,shift N 一次丢 N 个 |
| IFS | 改分隔符拆分/拼接,配合 read 解析结构文本 |
| 校验 | 正则/枚举/判空,早失败原则 |
| 错误 | 退出码约定,stderr 报错,die 函数统一出口 |
参数解析与错误处理决定了脚本的可用性。getopts 管选项、shift 管消费、IFS 管拆分、die 管报错,四件套到位后脚本对外部输入就足够从容。再往上走,就是工程化与规范。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。