- 文档
- 教程
- DevOps
- 运维
【免费下载链接】devops-exercises
Linux, Jenkins, AWS, SRE, Prometheus, Docker, Python, Ansible, Git, Kubernetes, Terraform, OpenStack, SQL, NoSQL, Azure, GCP, DNS, Elastic, Network, Virtualization. DevOps Interview Questions
导读
factors.md是 devops-exercises 仓库中 Shell Scripting 练习系列(位于 topics/shell/factors.md)的算术(Arithmetic)主题题目,其官方参考解在 topics/shell/solutions/factors.md。题目要求:给定一个数字,检测其是否以 2 或 3 为因子,并输出对应提示文本;当 2、3 均不是因子时则原样打印该数字本身。本篇将逐行拆解这份只有 4 行核心代码的精巧解法,讲透 Bash 算术展开((...))、&&/||短路求值、+=字符串拼接以及${var:-default}参数展开等关键语法,并结合仓库中其他同系列练习与实测输出,帮助你彻底掌握这一类"用 Bash 表达式代替 if 语句"的写法,可复制、可运行、可直接迁移到日常脚本中。
一、练习目标:三句话讲清需求
题目原文(topics/shell/factors.md)明确要求编写一个脚本,当给它传入一个数字时:
- 检测该数字是否有因子 2,若有则打印
one factor; - 检测该数字是否有因子 3,若有则打印
one factor...actually two!; - 若 2 和 3 都不是它的因子,则打印该数字本身。
注意需求是"叠加"而非"互斥":一个数字可能同时被 2 和 3 整除(例如 6、12),此时两行提示应同时成立,最终输出合并结果。整个练习在 topics/shell/README.md 的练习索引表中被归类为Arithmetic(算术)主题,难度标注为Basic(基础),与 Variables、Conditionals、For Loops、Functions 等练习共同构成仓库的 Shell 脚本入门训练链。
二、官方参考解:四行代码的精妙之处
仓库给出的参考解全文如下(topics/shell/solutions/factors.md):
#!/usr/bin/env bash (( $1 % 2 )) || res="one factor" (( $1 % 3 )) || res+="...actually two!" echo ${res:-$1}去掉 shebang 后真正参与逻辑的只有 3 行。要把这份解法真正"读懂、能复述、能举一反三",需要先理解它背后四个语法点,下面逐一展开。
1. shebang:为什么是#!/usr/bin/env bash
脚本首行#!/usr/bin/env bash是 shebang。仓库 topics/shell/README.md 的自测题中专门解释过:/bin/bash是 Linux 系统最常见的登录默认 shell,Bash(Bourne-again shell)功能丰富、语法完善、可执行绝大多数脚本,因此被广泛使用。而通过env间接查找bash的好处是:不硬编码/bin/bash的绝对路径,在 bash 安装于非标准位置(如/usr/local/bin/bash、或通过 Homebrew、conda 等安装)的系统上也能正确解析解释器,提高脚本的可移植性。
2. 算术展开((...)):$(( )) 的兄弟语法
Bash 支持两种算术求值写法:
- 命令替换形式
$(( 表达式 )):求值后把结果作为"值"替换出来,常用于赋值或输出,例如仓库 README 中给出的$(( 1 + 2 )),以及 sum.md 里的echo $(( $1 + $2 )); - 命令形式
(( 表达式 )):不输出结果,只对表达式求值并产生退出状态码——表达式值非 0 时状态为 1(失败),值为 0 时状态为 0(成功)。
factors 解法正是利用了命令形式的这一特性。(( $1 % 2 ))中%是取模运算符:当$1能被 2 整除时,$1 % 2等于 0,整条命令返回状态 0(成功);不能被 2 整除时返回非 0(失败)。这与 README 中另一道自测题的写法if [ $(($1 % 4)) -eq 0 ]; then在数学上是同一件事——判断n % k == 0等价于"k 是 n 的因子"——只是这里把 if 替换成了短路表达式。
3.||与&&的短路求值:把 if 写进一行
Bash 中cmd1 || cmd2表示"若 cmd1 失败(非 0 状态)则执行 cmd2",cmd1 && cmd2表示"若 cmd1 成功(状态 0)则执行 cmd2",二者都具备短路特性:cmd2 是否执行由 cmd1 的退出状态决定。
回到解法第一行(( $1 % 2 )) || res="one factor":
- 当
$1不能被 2 整除时,((...))返回失败,||右侧的赋值不执行,res保持未设置; - 当
$1能被 2 整除时,((...))返回成功,||短路,||右侧的赋值也不执行!
等等——这岂不是反了?这里正是本解法最容易让初学者困惑的"反直觉"点:res="one factor"恰恰是在能被整除时需要设置的。要解开这个矛盾,关键在于第三点——注意区分(( 1 ))与(( 0 )):(( 表达式 ))的退出状态规则是"表达式值为 0 时返回失败,非 0 时返回成功",与 C 语言逻辑相反。因此:
$1取值 | $1 % 2 | (( $1 % 2 ))退出状态 | ||结果 | res最终值 |
|---|---|---|---|---|
| 4 | 0 | 失败(1) | 执行右侧赋值 | one factor |
| 7 | 1 | 成功(0) | 跳过右侧赋值 | 未设置 |
也就是说:(( 取模结果 ))的失败状态恰好等于"取模结果为 0",即"能整除"。解法巧妙地用"取模命令失败"来代表"因子成立",从而让||在正确条件下触发赋值。这种写法在 argument_check.md 的[[ ${1} == "pizza" ]] && echo ... || echo ...和 README 中的三元写法[[ $a = 1 ]] && b="yes, equal" || b="nope"里是一致的思路——用短路表达式替代 if/else。
4.+=拼接与${res:-$1}参数展开
第二行(( $1 % 3 )) || res+="...actually two!":
res+=字符串是 Bash 的追加赋值运算符,等价于res="$res字符串"。当$1同时被 2 和 3 整除(如 6、12)时,res先被赋为one factor,再被追加为one factor...actually two!——这正是需求 1、2 叠加的体现;- 注意:若
res此前未设置(即不能被 2 整除),+=会直接把它当作空串处理,得到...actually two!,输出语义上略显怪异的...actually two!。这是参考解的一个已知行为边界:它把"有因子 3"与"因子 2 不存在"两种情况都视为可接受输出。后文将给出一个修正版本。
第三行echo ${res:-$1}用到 Bash 参数展开${var:-default}:当变量var未设置或为空时,展开结果为default;否则展开结果为var的值(注意:-不会真正修改变量本身)。这正好覆盖需求 3:当res从未被赋值(2、3 都不是因子,如 5、7)时,${res:-$1}展开为$1本身,脚本打印输入的数字;否则打印累积的因子提示文本。
三、运行验证:把参考解跑起来
将参考解保存为factors.sh并赋予执行权限(chmod +x factors.sh),或直接bash factors.sh <数字>运行。以 Bash 实测(仓库仓库当前环境为 Linux 默认 bash)结果如下:
| 输入 | 预期输出 | 实测输出 | 解释 |
|---|---|---|---|
| 4 | one factor | one factor | 4 % 2 == 0,仅命中因子 2 |
| 9 | ...actually two! | ...actually two! | 9 % 3 == 0,仅命中因子 3,res前缀为空 |
| 6 | one factor...actually two! | one factor...actually two! | 同时被 2、3 整除,拼接生效 |
| 12 | one factor...actually two! | one factor...actually two! | 同上 |
| 5 | 5 | 5 | 2、3 均非因子,${res:-$1}回退到数字本身 |
| 7 | 7 | 7 | 同上 |
实测同时印证了第二节的逐行分析:6 与 12 走的是"赋值 + 追加"路径,5 与 7 走的是"参数展开回退"路径,4 与 9 则分别只命中一个因子。你也可以用bash -x factors.sh 6打开 xtrace 逐步观察每条命令的退出状态与变量变化,这与仓库 topics/shell/README.md 中推荐的-x调试手段一致。
说明:
(( 表达式 ))的退出状态规则(值为 0 返回失败、非 0 返回成功)是 Bash 的既定语义,并非本项目自定义行为;上述实测输出与逐条推演完全吻合,可作为验证依据。
四、进阶辨析:参考解的边界与改进
参考解胜在极简,但有两个值得讨论的边界,理解它们能帮你写出更稳健的版本。
边界 1:输入校验缺失
脚本直接使用$1,未校验参数是否存在、是否为整数。参照仓库其他练习的校验惯例:
- sum.md 用正则
re='^[0-9]+$'配合! [[ $1 =~ $re && $2 =~ $re ]]校验两个入参必须是数字; - num_of_args.md 用
set -eu+case $# in ...)处理 0 个、1 个、多个参数三种情形; - directories_comparison.md 用
test $# -ne 2校验参数个数。
参考解的(( $1 % 2 ))在无参数或传入非整数时会报错((( ))把空串/非法内容按 0 处理,无参数时直接得到"能被 2 整除"从而输出one factor,行为不符合直觉)。因此生产环境建议先加参数个数与数字校验。
边界 2:单因子输出前缀问题
当数字仅被 3 整除(如 9)时,参考解输出...actually two!而非one factor...actually two!,因为res在第一步未被赋值、+=从空串开始。若希望语义更严谨,可改为先初始化空值再拼接,或显式区分两种情况:
#!/usr/bin/env bash # 更严谨的版本:仅被 3 整除时输出完整的 "one factor...actually two!" res="" (( $1 % 2 )) || res="one factor" if (( $1 % 3 == 0 )); then res+="...actually two!" fi echo "${res:-$1}"其中(( $1 % 3 == 0 ))是更直观的写法:判断"取模结果为 0",成功状态即代表"3 是因子",语义与 README 中的if [ $(($1 % 4)) -eq 0 ]完全一致。此版本同样实测通过,可作为对比学习材料。
五、把它放回训练体系中:这份练习的位置与价值
在 topics/shell/README.md 的练习索引表中,Factors 位列第 4 行,Topic 为 Arithmetic、难度为 Basic,位于 Hello World、Basic date(变量)与 Argument Check(条件判断)之间,前后呼应:
- 前置铺垫:变量赋值(
HW_STR="Hello World"、echo $(date))、位置参数$1的读取(README 自测题echo $1); - 同主题对照:算术运算的两种形态
$(( 1 + 2 ))与expr 1 + 2,以及因子判断if [ $(($1 % 4)) -eq 0 ]; - 后续进阶:Conditionals(Argument Check 的
[[ ]]+&&/||)、Loops(Count Chars 的 while)、Functions(Sum)以及参数处理(num_of_args 的case)。
可以看出,Factors 练习是仓库刻意安排的"以最少语法点覆盖最多核心机制"的一题:它在一个脚本内同时训练了算术展开、取模运算、短路求值、追加赋值、参数展开与默认值回退。掌握它之后,再看仓库中的echo "${1:-yay}"(README 变量自测题)、echo "Today is ${1:-a great day!}"(great_day.md)等默认值展开用法会毫无障碍。
六、总结
| 语法点 | 在本练习中的用法 | 仓库中的旁证 |
|---|---|---|
#!/usr/bin/env bash | 可移植地指定 Bash 解释器 | topics/shell/README.md shebang 自测题 |
(( 表达式 )) | 取模求值,用退出状态表达"能否整除" | README 中$(( 1 + 2 ))、if [ $(($1 % 4)) -eq 0 ] |
||短路 | 整除时触发赋值 | argument_check.md、README 三元写法 |
+= | 多因子提示文本拼接 | —(Bash 通用语法) |
${var:-default} | 无因子时回退打印数字 | README 中echo "${1:-yay}"、great_day.md |
一句话概括:factors 参考解用"取模命令的失败状态"反向表达"整除成立",再借||与${var:-default}把三分支的 if/else 压缩成三行表达式,是理解 Bash 控制流"表达式化"写法的绝佳入门样例。建议读者对照 topics/shell/factors.md 重新自测,再尝试自行实现带输入校验与完整输出的增强版,把本节知识点内化为自己的脚本技能。
- 文档
- 教程
- DevOps
- 运维
【免费下载链接】devops-exercises
Linux, Jenkins, AWS, SRE, Prometheus, Docker, Python, Ansible, Git, Kubernetes, Terraform, OpenStack, SQL, NoSQL, Azure, GCP, DNS, Elastic, Network, Virtualization. DevOps Interview Questions
相关推荐
devops-exercises 实战:用 Bash 算术与短路逻辑实现因数判断(Factors 练习全解析)
devops exercises 实战:用 Bash 算术与短路逻辑实现因数判断(Factors 练习全解析) 导读 本文围绕 devops exercises
文档教程DevOps运维devops-exercises 实战:用 Bash 编写主机存活检测脚本(host_status / It's Alive!)
devops exercises 实战:用 Bash 编写主机存活检测脚本(host_status / It's Alive!) 本指南以 devops exe
文档教程DevOps运维devops-exercises 仓库实战:用 Jenkins Groovy 脚本批量清理 Job 与过期 Build
devops exercises 仓库实战:用 Jenkins Groovy 脚本批量清理 Job 与过期 Build 导读 本文以 devops exerci
文档教程DevOps运维
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考