在 Bash 中使用 while 循环:语法与示例实战

循环是编程语言中最基础的概念之一。当你需要反复运行一系列命令,直到满足某个特定条件时,循环就非常有用。在深入之前,请确保你已经熟悉了基本的 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 速查表。

PatternExample
基础 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 脚本运行指南。