4. 脚本参数传递:位置参数、特殊变量与 getopts
脚本写出来,总不能把数据写死在代码里吧?那也太不灵活了。真正好用的脚本,得能接收外部输入。这一章我们就聊聊参数传递——说白了,就是怎么让脚本「听懂人话」。
核心要点:参数传递是脚本与外界交互的桥梁。掌握好它,你的脚本才能从「一次性工具」进化为「可复用组件」。
4.1 位置参数:$0 到 $9 的江湖
位置参数是最基础的参数传递方式。你运行脚本时跟在后面的那些值,脚本里用 $1、$2 就能拿到。
#!/bin/bash
# 一个简单的部署脚本
echo "脚本名: $0"
echo "目标服务器: $1"
echo "部署版本: $2"
echo "操作: $3"
运行一下:
$ ./deploy.sh prod-server v2.1 restart
脚本名: ./deploy.sh
目标服务器: prod-server
部署版本: v2.1
操作: restart
这里有个坑,我踩过好几次。$0 是脚本本身的路径,不是参数。如果你用相对路径执行,它返回的就是相对路径。用绝对路径,它就返回绝对路径。做日志记录时,这个差异可能会让你排查半天。
注意:位置参数最多支持 $9。想用第10个参数?得写成 ${10}。别问我怎么知道的——我曾经在循环里写 $10,结果它被解析成了 $1 后面跟个 "0"。
4.2 参数个数与遍历:$#、$@、$*
这三个变量,我刚开始学的时候总觉得差不多。用多了才发现,它们各有各的脾气。
| 变量 | 含义 | 典型场景 |
|---|---|---|
$# |
参数个数 | 校验用户是否传够了参数 |
$@ |
所有参数(每个参数独立) | 循环遍历每个参数 |
$* |
所有参数(当作一个字符串) | 直接传给另一个命令 |
看个例子就明白了:
#!/bin/bash
echo "参数个数: $#"
echo "使用 \$@ 遍历:"
for arg in "$@"; do
echo " - [$arg]"
done
echo "使用 \$* 遍历:"
for arg in "$*"; do
echo " - [$arg]"
done
运行 ./test.sh a "b c" d,输出差异很明显:
参数个数: 3
使用 $@ 遍历:
- [a]
- [b c] # 保持了引号内的空格
- [d]
使用 $* 遍历:
- [a b c d] # 所有参数粘在一起了
我个人习惯在循环里用 "$@",带引号那种。它能保留参数中的空格,不会把 "b c" 拆成两个。这个细节,当年让我在日志解析脚本上折腾了一下午。
4.3 返回值与退出码:$?
$? 记录上一条命令的退出码。0 表示成功,非 0 表示出错了。
#!/bin/bash
ping -c 1 8.8.8.8 > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "网络通了"
else
echo "网络不通,检查一下"
fi
小技巧:检查 $? 要趁早。每执行一条新命令,它就会被覆盖。我习惯把关键步骤的退出码存到变量里:ret=$?,后面慢慢分析。
4.4 高级参数解析:getopts 实战
位置参数虽然简单,但参数一多就乱了。用户得记住 $1 是服务器、$2 是版本……这体验太差了。getopts 就是来解决这个问题的——它支持 -h、-v 这种风格。
#!/bin/bash
usage() {
echo "用法: $0 -s <服务器> -v <版本> [-d] [-h]"
echo " -s 目标服务器(必填)"
echo " -v 部署版本(必填)"
echo " -d 调试模式"
echo " -h 帮助信息"
exit 1
}
server=""
version=""
debug=false
while getopts "s:v:dh" opt; do
case $opt in
s) server="$OPTARG" ;;
v) version="$OPTARG" ;;
d) debug=true ;;
h) usage ;;
?) usage ;;
esac
done
# 校验必填参数
if [ -z "$server" ] || [ -z "$version" ]; then
echo "错误:-s 和 -v 是必填参数"
usage
fi
echo "部署到: $server"
echo "版本: $version"
[ "$debug" = true ] && echo "调试模式已开启"
运行效果:
$ ./deploy.sh -s web01 -v 2.1.0 -d
部署到: web01
版本: 2.1.0
调试模式已开启
$ ./deploy.sh -h
用法: ./deploy.sh -s <服务器> -v <版本> [-d] [-h]
-s 目标服务器(必填)
-v 部署版本(必填)
-d 调试模式
-h 帮助信息
getopts 的坑:
- 选项字母后面的冒号表示「这个选项需要参数」,比如
s:表示-s后面必须跟值 - 不支持长选项(如
--server),那是getopt的活 - 选项顺序无所谓,但参数值不能跟选项粘在一起(
-sweb01可能不生效)
4.5 知识体系总览
下面这张图,把本章的知识点串起来了。你可以把它当作参数传递的「地图」——遇到问题先看它,定位到具体模块再细查。
4.6 避坑指南与最佳实践
做运维这些年,参数传递这块我踩过的坑,能写满一页纸。挑几个典型的说说:
我的经验:
- 引号别省:所有变量引用都加双引号,除非你明确知道不加的后果。
"$@"和$@是两个东西。 - 参数校验前置:脚本开头就检查参数,别等到执行到一半才发现缺参数。
- 帮助信息要全:写
-h选项,把用法、参数说明、示例都写上。用户会感谢你的。 - 退出码要规范:0 成功,1 通用错误,2 参数错误。别乱用退出码。
我曾经写过一个数据库备份脚本,参数解析用了位置参数。后来需求变了,要加新参数,结果所有调用方都得改顺序。那次之后,但凡超过两个参数,我必上 getopts。你想想看,改一个脚本要通知十几个团队改调用方式,那画面太美我不敢看。
一句话总结:参数传递是脚本的「门面」。门面做得好,用户用得爽;门面做得差,天天被吐槽。花 10 分钟把参数解析写好,能省下后面 10 小时的沟通成本。