第 4 章 · 02 条件循环与函数


文档摘要

第 4 章 · 02 条件循环与函数 变量是脚本的血肉,控制流才是骨骼。本节把四大控制结构一次讲清:if/[[ ]] 条件判断与正则匹配、for/while 循环、case 分支、函数与 local 局部变量,再加上 read 交互输入、进程替换与三元表达式三个进阶技巧。 学习目标 用 if/[[ ]] 编写条件分支,用 = 做正则匹配 用 for/while 处理参数列表与逐行输入 定义函数并用 local 隔离局部变量 用 case 按模式分派分支,用 read 获取用户输入 理解进程替换 <( ) 与管道、三元表达式的适用场景 一、条件判断:if 与 [[ ]] Shell 的条件判断有两种括号写法: 本质是 test 命令的别名(所以条件两侧必须有空格),而 是 Bash

第 4 章 · 02 条件循环与函数

变量是脚本的血肉,控制流才是骨骼。本节把四大控制结构一次讲清:if/[[ ]] 条件判断与正则匹配、for/while 循环、case 分支、函数与 local 局部变量,再加上 read 交互输入、进程替换与三元表达式三个进阶技巧。

学习目标

  • 用 if/[[ ]] 编写条件分支,用 =~ 做正则匹配
  • 用 for/while 处理参数列表与逐行输入
  • 定义函数并用 local 隔离局部变量
  • 用 case 按模式分派分支,用 read 获取用户输入
  • 理解进程替换 <( ) 与管道、三元表达式的适用场景

一、条件判断:if 与 [[ ]]

Shell 的条件判断有两种括号写法:

if [ 条件 ]; then ... fi # 老式 test 命令 if [[ 条件 ]]; then ... fi # Bash 扩展,推荐

[ 本质是 test 命令的别名(所以条件两侧必须有空格),而 [[ ]] 是 Bash 内建语法,支持更多特性且无需转义。两者都支持数字比较(-eq/-ne/-gt/-lt)、字符串比较(= / !=)与文件判断(-f 存在且为文件、-d 目录、-s 非空、-x 可执行)。

[[ ]] 的杀手锏有两个。一是逻辑运算直接用 && 和 ||,而 [ ] 里必须写 -a/-o 且容易踩优先级坑:

[[ -f /etc/passwd && -r /etc/passwd ]] && echo "可读"

二是正则匹配 =~,判断"参数是否为数字"只需一行:

regex='^[0-9]+$' if [[ ${var//*.} =~ $regex ]]; then ... fi

这里 ${var//*.} 提取扩展名后整体匹配"纯数字"正则。注意正则模式建议放进变量再引用,避免与 Bash 的展开规则打架。

条件判断还有一种"一行版"——三元表达式。Shell 没有真正的三元运算符,但可以用 && / || 短路模拟:

[[ $a = 1 ]] && b="yes, equal" || b="nope"

$a 为 1 时执行赋值 yes,否则赋 nope。这是 if 的紧凑糖衣,适合简单二选一;逻辑复杂时还是老老实实写完整 if 更可读。

二、循环:for 与 while

for 循环最常见的用途是遍历"参数列表"或"命令输出":

for file in "$@"; do # 遍历所有参数 echo "处理 $file" done for f in /etc/*.conf; do # 遍历通配符展开的文件 stat -c '%s' "$f" # 输出每个文件的大小 done

for 的默认遍历对象就是位置参数:不写 in "$@" 时,for x 自动遍历 "@",这与前面强调的"@ 是数组"呼应——for 需要的是可切分的序列。

while 循环则适合"条件驱动"的场景,最经典的是配合 read 逐行读入:

while read line; do echo "${#line} 个字符" # ${#var} 求长度 done < input.txt

循环与 read 的组合是 Shell 处理文本行的标准姿势:把文件重定向进循环,read 每次消费一行,循环体逐行处理。处理大文件时这种写法比 for line in $(cat file) 更安全——后者会把整文件读进内存,且按空白切词会丢失行结构。

三、函数与局部变量

函数把一段逻辑命名复用。两种定义方式等价:

function foo { ... } foo() { ... }

函数内的变量默认是全局的——这常常导致"函数改坏外部变量"的幽灵 bug。解决方案是 local 关键字,把变量限定在函数作用域内。经典考题:

#!/usr/bin/env bash NINJA_TURTLE=Donatello function the_best_ninja_turtle { local NINJA_TURTLE=Michelangelo echo $NINJA_TURTLE } NINJA_TURTLE=Raphael the_best_ninja_turtle

输出是 Michelangelo:函数内 local 声明遮蔽了外部同名变量,函数结束后外部变量不受影响。这个例子同时考了三件事——local 的遮蔽、函数调用时机、以及变量赋值无空格规则。

函数"返回值"有两条路径:一是用 return 返回退出码(0-255 的整数状态),二是用 echo 输出"结果值",调用方用命令替换接住:result=$(myfunc)。前者表达成功与否,后者传递数据,两者分工清晰。

四、case:多路分支

if/elif 链条超过三路时,可读性急剧下降,此时用 case 按模式分派:

case $1 in start) echo "启动服务" ;; stop) echo "停止服务" ;; restart) echo "重启服务" ;; *) echo "用法: $0 {start|stop|restart}" ;; esac

case 的匹配是通配模式而非精确相等:* 兜底所有未命中项,foo|bar) 可一分支匹配多个值。判断"参数个数"的练习(num_of_args)就是 case 的经典应用:按 $# 的值分派 0 个/1 个/多个参数的不同输出。

五、交互输入:read

read 从标准输入读取一行,存入变量:

read x # 等待用户输入,存入 x read -p "名字: " name # -p 打印提示符

read 默认按空白切分到多个变量:read a b c 会把一行切成三份。前面讲过的 while read line 逐行处理,本质就是 read 与重定向、循环的三合一。

六、进阶技巧:进程替换

进程替换 <(command) 把命令的输出伪装成"一个文件",从而让只接受文件参数的命令直接消费命令输出:

diff <(ls /tmp) <(ls /var/tmp)

这里 diff 要求两个文件参数,而数据源是两个 ls 的输出。管道 | 做不到这一点——管道只能把一个输出喂给一个输入,diff 却要同时读两个流。进程替换的场景正是"一个命令需要多个命令的输出"或"命令不支持标准输入"。

与进程替换对应的还有 >(command)(把命令当文件写入),用于重定向场景,但使用频率低得多。

小结

本节搭起了脚本的"语法层":[[ ]] 与 =~ 让脚本会判断、会正则;for/while 让脚本会重复;函数 + local 让脚本会复用且不污染全局;case 让多路分支清晰;read 让脚本会交互;进程替换与三元表达式则提供了管道之外的组合手段。语法层齐备,脚本已经"能跑",但还"不放心"——下一节用练习和调试把它打磨到"能交付"。

下一节预告

第 4 章第 3 节《练习与调试最佳实践》将 12 道经典脚本练习按难度分组精讲,并给出 set -e 防御、bash -x 调试、常见坑清单与 bats 自动化测试四件工程化工具。


发布者: 作者: 灏天文库 转发
评论区 (0)
U