
循环是编程语言中最基础的概念之一。当你需要反复运行一系列命令,直到满足某个特定条件时,循环就非常有用。在深入之前,请确保你已经熟悉了基本的 Linux 命令。
在 Bash 等脚本语言中,循环对于自动化重复性的任务十分方便。Bash 脚本中有三种基本的循环结构:for 循环、while 循环和 until 循环。
本教程介绍 Bash 中 while 循环的基础知识。我们还会演示如何使用 break 和 continue 语句来改变循环的执行流程。
while 循环语法
只要给定的条件判断为真,while 循环就会反复执行给定的一组命令(次数事先并不确定)。
Bash 的 while 循环采用以下形式:
while [CONDITION]
do
[COMMANDS]
done
while 语句以 while 关键字开头,后面跟着条件表达式。
在执行命令之前会先对条件进行求值。如果条件为真,则执行命令;反之,如果条件为假,循环就会终止,并将程序控制权交给后面的命令。
在下面的示例中,每次迭代都会打印变量 i 的当前值并将其加 1。条件使用 -le 比较运算符来检查 i 是否小于或等于 2。
i=0
while [ $i -le 2 ]
do
echo Number: $i
((i++))
done
只要 i 小于或等于 2,循环就会持续迭代,并产生如下输出:
Number: 0
Number: 1
Number: 2
无限 while 循环
无限循环是指永不终止、会无限重复执行的循环。如果条件始终求值为真,就会产生无限循环。
在下面的示例中,我们使用内建命令 : 来创建一个无限循环。: 始终返回真,换成任何始终不会失败的其他语句,效果也是一样的。
while :
do
echo "Press <CTRL+C> to exit."
sleep 1
done
上面的 while 循环会无限运行下去。你可以按下 CTRL+C 来终止循环。
下面是一行等价的写法:
while :; do echo 'Press <CTRL+C> to exit.'; sleep 1; done
true 内建命令的工作方式与此相同,在较长的脚本中可读性更好:
while true
do
echo "Press <CTRL+C> to exit."
sleep 1
done
: 和 true 都是 Shell 内建命令,因此每次迭代都不会启动新进程。选择哪一个只是可读性的问题。
无限循环也是实现 do...while 循环(Bash 并不提供)行为的方式。把测试放到循环体的末尾,并用 break 退出:
i=5
while true
do
echo "Number: $i"
((i++))
[ $i -lt 3 ] || break
done
Number: 5
条件从第一次迭代起就为假,但循环体仍然执行了一次,因为测试发生在命令之后,而不是之前。
其他条件形式
到目前为止的示例都使用 [ ](单括号测试)。Bash 也接受 [[ ]] 用于更丰富的字符串比较,以及 (( )) 用于算术求值。
当条件纯属数值时,使用 (( ))。(( )) 内部的变量不需要加 $ 前缀,语法也更接近其他编程语言:
i=0
while (( i < 3 ))
do
echo "Number: $i"
((i++))
done
当条件涉及字符串模式,或用到 && 和 || 运算符时,使用 [[ ]]。下面的循环会读取 notes.txt,并在读到恰好为 END 的那一行时立即停止。
用以下内容创建 notes.txt:
first line
second line
END
last line
运行下面的循环。将 IFS 设置为空字符串可以保留行首和行尾的空白,这样像 END 这样的行就不会使循环停止:
while IFS= read -r line && [[ "$line" != "END" ]]
do
echo "$line"
done < notes.txt
循环会打印 END 之前的这两行:
first line
second line
文件中保存了 END 以及它之后的一行,这两行都不会被打印。read 命令在读到 END 时成功,但条件的后半部分随后失败,于是循环在循环体再次运行前就停止了。
逐行读取文件
while 循环最常见的用途之一,就是逐行读取文件、数据流或变量。我们关于逐行读取文件的指南对该主题有更深入的探讨,包括 mapfile 替代方案以及不以换行符结尾的文件。
下面是一个逐行读取 /etc/passwd 文件并打印每一行的示例:
file=/etc/passwd
while read -r line; do
echo "$line"
done < "$file"
这里没有用条件来控制 while 循环,而是使用输入重定向(< "$file")把一个文件传给 read 命令,由它来控制循环。while 循环会一直运行,直到读完最后一行。
逐行读取文件时,务必对 read 命令加上 -r 选项,以防止反斜杠被当作转义字符处理。
默认情况下,read 命令会去除开头和结尾的空白字符(空格和制表符)。在 read 前面将 IFS 设为空字符串,可以阻止这种行为:
file=/etc/passwd
while IFS= read -r line; do
echo "$line"
done < "$file"
管道与子 Shell 陷阱
当你把数据通过管道送入 while 循环时,循环会在一个子 Shell 中运行,因此循环内部的任何变量改动在循环退出后都会丢失:
count=0
seq 1 5 | while read -r n; do
((count++))
done
echo "$count"
0
要让变量保留在当前 Shell 中,请改用输入重定向或进程替换:
count=0
while read -r n; do
((count++))
done < <(seq 1 5)
echo "$count"
5
break 与 continue 语句
break 和 continue 语句可用于控制 while 循环的执行。
break 语句
break 语句会终止当前的循环,并将程序控制权交给被终止循环后面的命令。它通常用于在满足某个条件时终止循环。
在下面的示例中,一旦当前迭代的项等于 2,循环的执行就会被中断。
i=0
while [ $i -lt 5 ]
do
echo "Number: $i"
((i++))
if [[ "$i" == '2' ]]; then
break
fi
done
echo 'All Done!'
Number: 0
Number: 1
All Done!
continue 语句
continue 语句会退出循环的当前一次迭代,并将程序控制权交给循环的下一次迭代。
在下面的示例中,一旦当前迭代的项等于 2,continue 语句就会使执行回到循环开头,并从下一次迭代继续。
i=0
while [ $i -lt 5 ]
do
((i++))
if [[ "$i" == '2' ]]; then
continue
fi
echo "Number: $i"
done
echo 'All Done!'
Number: 1
Number: 3
Number: 4
Number: 5
All Done!
快速参考
如需可打印的快速参考,请参阅 Bash 速查表。
| Pattern | Example |
|---|---|
| 基础 while 循环 | while [ $i -le 10 ]; do ...; done |
| 算术条件 | while (( i < 10 )); do ...; done |
| 无限循环 | while :; do ...; done or while true; do ...; done |
| 逐行读取文件 | while read -r line; do ...; done < file |
| 保留空白字符 | while IFS= read -r line; do ...; done < file |
| 满足条件时跳出 | if [[ ... ]]; then break; fi |
| 跳过本次迭代 | if [[ ... ]]; then continue; fi |
故障排查
循环永不终止:请确保条件中的变量在循环体内被更新,并且条件最终会变为假。
integer expression expected(期望整数表达式)错误:这通常意味着某个数值变量为空,或含有非数字的文本。使用前先初始化计数器,例如 i=0。
读取行时丢失了空白:使用 IFS= read -r line,并用 echo "$line" 打印,以保留行首空格和反斜杠。
常见问题
while 和 until 有什么区别?while 循环在条件为真时运行;until 循环在条件为假时运行,它是 while 的逻辑相反形式。
如何退出无限 while 循环?在终端中按下 CTRL+C 即可中断循环;在脚本内部,可在 if 块里使用 break 语句,在满足条件时退出。
我能否用 while 循环固定次数?可以。初始化一个计数器变量,并在每次迭代中用 ((i++)) 让它自增。使用 [ $i -le 10 ] 这样的条件来控制循环运行的次数。有关计数器示例,请参阅我们的自增与自减指南。
为什么我的 while 循环不会终止?条件始终求值为真。请检查被测试的变量是否真的在循环体内发生了变化,以及条件最终是否会变为假。
Bash 有 do-while 循环吗?没有。Bash 没有在第一次测试之前先运行循环体的结构。要获得相同的行为,可以写一个 while true 的无限循环,把条件放在循环体末尾,并用 break 退出循环。
我可以嵌套 while 循环吗?可以。你可以把一个 while 循环放在另一个 while 循环里面。为每个循环使用独立的计数器变量,以避免冲突。
总结
只要条件为真,while 循环就会反复执行给定的一组命令。要了解更多关于运行脚本的内容,请参阅我们的 Bash 脚本运行指南。