在 devops-exercises 中掌握 Bash 参数默认值:从 "Great Day" 练习理解 `${1:-default}` 表达式
在 devops-exercises 中掌握 Bash 参数默认值:从 "Great Day" 练习理解 ${1:-default} 表达式
导读
本篇以 devops-exercises 仓库的 shell 脚本入门练习 great_day.md 为核心,带你用一行 Bash 代码实现"有参数输出参数、无参数输出默认文案"的经典逻辑。通过拆解 ${1:-a great day!} 这条参数扩展表达式,你将彻底掌握 Bash 位置参数、参数默认值与空值处理的核心机制,并借此串联起仓库内 Hello World、Basic date、Argument Check、Number of Arguments 等系列入门练习,为后续编写健壮的 Shell 脚本打下坚实基础。
一、练习目标:用最少的代码完成条件输出
仓库中的 great_day.md 提出了一个非常精简的练习目标:
- 编写一个脚本,默认打印
Today is a great day!; - 如果传入了一个日期/星期名称作为参数,则打印
Today is <given day>,其中<given day>就是传入的参数值; - 特别注明:无需校验传入的参数是否为合法日期名称,这意味着练习聚焦于"参数读取 + 默认值回退"这一语法本身,而不是输入校验。
这类"有则用之、无则给默认值"的需求在真实运维脚本中极为常见——例如日志脚本默认输出当天日期、部署脚本默认使用 latest 标签等。而该练习的精妙之处在于:它要求你在不写 if 分支的情况下完成任务,从而倒逼你掌握 Bash 的参数扩展(Parameter Expansion)语法。
二、官方解法:一行代码的优雅实现
仓库中对应的 解决方案 给出了如下实现:
#!/usr/bin/env bash
echo "Today is ${1:-a great day!}"
整个脚本的核心就是这一行 echo。我们来拆解它的每个组成部分:
$1:第一个位置参数,即脚本被调用时传入的第一个参数;${1:-a great day!}:参数扩展表达式,含义是——如果$1未设置或为空,则使用a great day!作为回退值;- 外层
echo将表达式的结果输出到标准输出。
以脚本文件 great_day.sh 为例,实际运行效果如下(这是我在当前环境中的真实验证结果):
$ ./great_day.sh
Today is a great day!
$ ./great_day.sh Monday
Today is Monday
$ ./great_day.sh "2026-09-30"
Today is 2026-09-30
可以看到:不传参数时打印默认文案,传入参数时打印参数本身,完全符合练习目标。
三、深入原理:Bash 参数扩展 ${var:-default} 详解
3.1 位置参数:脚本如何接收外部输入
Bash 中,脚本执行时传入的参数会依次被存入特殊变量 $1、$2、$3……它们统称为"位置参数"(Positional Parameters)。与之配套的关键变量还有:
| 变量 | 含义 | 典型用途 |
|---|---|---|
$0 |
脚本自身的名字 | 打印使用说明:Usage: $0 <argument> |
$# |
位置参数的个数 | 判断参数数量 |
$@ |
所有参数,作为一个"数组"展开 | 逐个处理参数 |
$* |
所有参数,拼接成一个字符串 | 一次性整体输出 |
在 topics/shell/README.md 的"Shell Scripting - Variables"小节中,仓库明确给出了 $@ 与 $* 的核心区别:$@ 是所有参数的数组形式,$* 是所有参数拼成的单个字符串。这一区别在需要遍历参数或保持每个参数独立性时至关重要。
3.2 :- 运算符:未设置与空值都算"缺失"
${1:-a great day!} 的完整语法是:
${parameter:-word}
其语义为:如果 parameter 未设置(unset)或者值为空字符串(null),则展开结果为 word;否则展开结果为 parameter 本身的值。
与它极易混淆的另一个写法是 - 运算符:
${parameter-word}
两者的差别仅在于:${parameter-word} 只在参数未设置时才回退到默认值,若参数存在但为空字符串,则仍会输出空字符串;而 ${parameter:-word} 把"空字符串"也视为缺失,一并回退。对于 great_day.sh 这类脚本,使用 :- 更加稳妥——例如用户执行 ./great_day.sh "" 时,${1-a great day!} 会打印出一个空白的 Today is ,而 :- 版本依然会正确打印 Today is a great day!。
除 :- 外,Bash 还提供一组配套的参数扩展运算符,理解它们可以帮助你触类旁通:
| 表达式 | 行为 |
|---|---|
${parameter:-word} |
参数未设置或为空时,使用 word 作为结果(不改变参数本身) |
${parameter:=word} |
参数未设置或为空时,把 word 赋值给该参数,再作为结果 |
${parameter:?word} |
参数未设置或为空时,输出 word 并令脚本报错退出 |
${parameter:+word} |
参数已设置且非空时才使用 word,否则输出空 |
3.3 为什么这个写法优于显式 if
练习刻意要求不使用 if 分支,背后是一个重要的工程理念:脚本行数越少,出错面越小。如果用传统写法,同等逻辑至少需要 4 行:
#!/usr/bin/env bash
if [ -z "${1}" ]; then
echo "Today is a great day!"
else
echo "Today is ${1}"
fi
而参数扩展版本把"判断 + 回退"压缩进一次展开,代码更短、可读性更强,也避免了 [ -z "$1" ] 这类条件测试中引号缺失导致的空白分词问题。当然,if 写法在需要执行复杂分支逻辑时依然不可替代,参数扩展更适合"单值回退"场景——两者互为补充,而非互斥。
四、运行与验证:从脚本文件到命令行
4.1 编写并运行脚本
将上文解法保存为 great_day.sh 后,在当前目录执行:
$ bash great_day.sh # 或先赋予执行权限:chmod +x great_day.sh 后执行 ./great_day.sh
脚本首行的 #!/usr/bin/env bash 是 Shebang(解释器声明):它告诉内核用哪个程序来执行该脚本。使用 env bash 而非直接写死 /bin/bash 的好处是,脚本会优先从 PATH 中找到的 bash 来运行,增强了跨环境兼容性。仓库 topics/shell/README.md 的自评小节也专门解释了 Shebang 的作用:/bin/bash 是最常用的登录 shell,Bourne-again shell 功能完善、语法成熟,可执行绝大多数脚本。
4.2 边界情况验证
我在当前 Linux 环境下实际运行了以下用例,结果与预期完全一致:
# 用例 1:无参数 → 输出默认文案
$ great_day_fn
Today is a great day!
# 用例 2:传入一个参数 → 输出参数值
$ great_day_fn Monday
Today is Monday
# 用例 3:传入一个看起来"不像星期"的值 → 照样输出(练习明确不要求校验)
$ great_day_fn "2026-09-30"
Today is 2026-09-30
# 用例 4:传入空字符串 → 因使用 :- 运算符,仍回退到默认文案
$ great_day_fn ""
Today is a great day!
用例 4 正是 :- 运算符与 - 运算符的关键差异点,建议你亲手对比体验。此外,若传入多个参数(如 great_day.sh Monday Tuesday),脚本只取 $1,其余参数会被忽略;如需处理全部参数,则应使用 $@。
五、在仓库练习序列中的定位:从变量到条件到分支
Great Day 练习在 topics/shell/README.md 的练习表格中位于"Variables(变量)"主题之下,属于基础难度,是 shell 练习序列的第三课(紧跟在 Hello World 与 Basic date 之后)。与它相邻的练习构成了一个递进的学习路径:
- hello_world.md:定义变量并重定向输出——对应 解决方案 中的
HW_STR="Hello World"与echo $HW_STR > amazing_output.txt,让你先熟悉变量赋值; - basic_date.md:用命令替换
$(date)把当前日期存入变量——对应 解决方案 的echo $(date) > the_date.txt,引入命令替换语法; - great_day.md(本文):引入位置参数
$1与参数扩展${1:-...},是"从变量走向外部输入"的关键一步; - argument_check.md:用
<a href="https://link.gitcode.com/i/28d44fc28e4adebf35ec6827ab3876bf" target="_blank">[ ${1} == "pizza" ]] && echo ... || echo ...实现单行条件判断——对应 [解决方案,本质仍是参数扩展与&&/||短路求值的组合; - num_of_args.md:用
case $# in按参数个数分派逻辑——对应 解决方案 中set -eu与case语句的组合,开始涉及脚本健壮性(set -e让脚本在命令失败时立即退出)。
值得一提的是,topics/shell/README.md 的 Variables 自评区还有一个与本文几乎同源的经典问答题:"写一个脚本,除非传入了参数就打印该参数,否则打印 yay",其标准答案正是 echo "${1:-yay}"。这说明 ${1:-...} 模式是仓库作者反复强调的 Bash 核心考点,值得你重点记忆。
六、延伸思考:这个一行脚本还能怎么玩
掌握 ${1:-default} 之后,可以立刻把它迁移到更多真实场景:
#!/usr/bin/env bash
# 默认日志目录 + 用户可覆盖
LOG_DIR="${1:-/var/log/myapp}"
echo "Writing logs to: ${LOG_DIR}"
# 默认环境 + 用户可覆盖
ENV="${1:-production}"
echo "Deploying to: ${ENV}"
同时建议结合 topics/shell/README.md 的 Troubleshooting 小节养成调试习惯:用 bash -x great_day.sh Monday 逐行追踪脚本执行过程,观察参数展开前后的实际值;或在脚本中临时加入 echo 语句打印 ${1:-a great day!} 的展开结果。对于更复杂的 shell 测试,仓库自评区还提到了 bats 这一测试工具,可作为你后续进阶的参考方向。
总结
Great Day 练习虽然只有一行核心代码,却精准覆盖了 Bash 脚本开发的三块基石:位置参数($1、$@、$#)、参数扩展(${1:-word})以及 Shebang 运行机制。它教会你的不仅是一个语法点,更是一种"用语言特性替代冗余分支"的脚本设计思路。把这一行代码连同仓库中 Hello World、Argument Check、Number of Arguments 等姊妹练习串起来练习,你就能快速建立起对 Bash 输入处理与默认值管理的完整认知,这正是 devops-exercises 为入门者铺设的成长路径。