循环

阮一峰 ... Linux
  • Bash
大约 7 分钟

Bash 提供三种循环语法 forwhileuntil

# while 循环

while 循环有一个判断条件,只要符合条件,就不断循环执行指定的语句。

while condition; do
  commands
done
1
2
3

上面代码中,只要满足条件 condition,就会执行命令 commands。然后,再次判断是否满足条件 condition,只要满足,就会一直执行下去。只有不满足条件,才会退出循环。

循环条件 condition 可以使用 test 命令,跟 if 结构的判断条件写法一致。

#!/bin/bash

number=0
while [ "$number" -lt 10 ]; do
  echo "Number = $number"
  number=$((number + 1))
done
1
2
3
4
5
6
7

上面例子中,只要变量 $number 小于 10,就会不断加 1,直到 $number 等于 10,然后退出循环。

关键字 do 可以跟 while 不在同一行,这时两者之间不需要使用分号分隔。

while true
do
  echo 'Hi, while looping ...';
done
1
2
3
4

上面的例子会无限循环,可以按下 Ctrl + c 停止。

while 循环写成一行,也是可以的。

while true; do echo 'Hi, while looping ...'; done
1

while 的条件部分也可以是执行一个命令。

while echo 'ECHO'; do echo 'Hi, while looping ...'; done
1

上面例子中,判断条件是 echo 'ECHO'。由于这个命令总是执行成功,所以上面命令会产生无限循环。

while 的条件部分可以执行任意数量的命令,但是执行结果的真伪只看最后一个命令的执行结果。

while true; false; do echo 'Hi, looping ...'; done
1

上面代码运行后,不会有任何输出,因为 while 的最后一个命令是 false

# until 循环

until 循环与 while 循环恰好相反,只要不符合判断条件(判断条件失败),就不断循环执行指定的语句。一旦符合判断条件,就退出循环。

until condition; do
  commands
done
1
2
3

关键字 do 可以与 until 不写在同一行,这时两者之间不需要分号分隔。

until condition
do
  commands
done
1
2
3
4

下面是一个例子。

$ until false; do echo 'Hi, until looping ...'; done
Hi, until looping ...
Hi, until looping ...
Hi, until looping ...
^C
1
2
3
4
5

上面代码中,until 的部分一直为 false,导致命令无限运行,必须按下 Ctrl + c 终止。

#!/bin/bash

number=0
until [ "$number" -ge 10 ]; do
  echo "Number = $number"
  number=$((number + 1))
done
1
2
3
4
5
6
7

上面例子中,只要变量 number 小于 10,就会不断加 1,直到 number 大于等于 10,就退出循环。

until 的条件部分也可以是一个命令,表示在这个命令执行成功之前,不断重复尝试。

until cp $1 $2; do
  echo 'Attempt to copy failed. waiting...'
  sleep 5
done
1
2
3
4

上面例子表示,只要 cp $1 $2 这个命令执行不成功,就 5 秒钟后再尝试一次,直到成功为止。

until 循环都可以转为 while 循环,只要把条件设为否定即可。上面这个例子可以改写如下。

while ! cp $1 $2; do
  echo 'Attempt to copy failed. waiting...'
  sleep 5
done
1
2
3
4

一般来说,until 用得比较少,完全可以统一都使用 while

# for...in 循环

for...in 循环用于遍历列表的每一项。

for variable in list
do
  commands
done
1
2
3
4

上面语法中,for 循环会依次从 list 列表中取出一项,作为变量 variable,然后在循环体中进行处理。

关键词 do 可以跟 for 写在同一行,两者使用分号分隔。

for variable in list; do
  commands
done
1
2
3

下面是一个例子。

#!/bin/bash

for i in word1 word2 word3; do
  echo $i
done
1
2
3
4
5

上面例子中,word1 word2 word3 是一个包含三个单词的列表,变量 i 依次等于 word1word2word3,命令 echo $i 则会相应地执行三次。

列表可以由通配符产生。

for i in *.png; do
  ls -l $i
done
1
2
3

上面例子中,*.png 会替换成当前目录中所有 PNG 图片文件,变量 i 会依次等于每一个文件。

列表也可以通过子命令产生。

#!/bin/bash

count=0
for i in $(cat ~/.bash_profile); do
  count=$((count + 1))
  echo "Word $count ($i) contains $(echo -n $i | wc -c) characters"
done
1
2
3
4
5
6
7

上面例子中,cat ~/.bash_profile 命令会输出 ~/.bash_profile 文件的内容,然后通过遍历每一个词,计算该文件一共包含多少个词,以及每个词有多少个字符。

in list 的部分可以省略,这时 list 默认等于脚本的所有参数 $@。但是,为了可读性,最好还是不要省略,参考下面的例子。

for filename; do
  echo "$filename"
done

# 等同于

for filename in "$@" ; do
  echo "$filename"
done
1
2
3
4
5
6
7
8
9

在函数体中也是一样的,for...in 循环省略 in list 的部分,则 list 默认等于函数的所有参数。

# for 循环

for 循环还支持 C 语言的循环语法。

for (( expression1; expression2; expression3 )); do
  commands
done
1
2
3

上面代码中,expression1 用来初始化循环条件,expression2 用来决定循环结束的条件,expression3 在每次循环迭代的末尾执行,用于更新值。

注意,循环条件放在双重圆括号之中。另外,圆括号之中使用变量,不必加上美元符号 $

它等同于下面的 while 循环。

(( expression1 ))
while (( expression2 )); do
  commands
  (( expression3 ))
done
1
2
3
4
5

下面是一个例子。

for (( i=0; i<5; i=i+1 )); do
  echo $i
done
1
2
3

上面代码中,初始化变量 i 的值为 0,循环执行的条件是 i 小于 5。每次循环迭代结束时,i 的值加 1。

for 条件部分的三个语句,都可以省略。

for ((;;))
do
  read var
  if [ "$var" = "." ]; then
    break
  fi
done
1
2
3
4
5
6
7

上面脚本会反复读取命令行输入,直到用户输入了一个点(.)位为止,才会跳出循环。

# break,continue

Bash 提供了两个内部命令 breakcontinue,用来在循环内部跳出循环。

break 命令立即终止循环,程序继续执行循环块之后的语句,即不再执行剩下的循环。

#!/bin/bash

for number in 1 2 3 4 5 6
do
  echo "number is $number"
  if [ "$number" = "3" ]; then
    break
  fi
done
1
2
3
4
5
6
7
8
9

上面例子只会打印 3 行结果。一旦变量 $number 等于 3,就会跳出循环,不再继续执行。

continue 命令立即终止本轮循环,开始执行下一轮循环。

#!/bin/bash

while read -p "What file do you want to test?" filename
do
  if [ ! -e "$filename" ]; then
    echo "The file does not exist."
    continue
  fi

  echo "You entered a valid file.."
done
1
2
3
4
5
6
7
8
9
10
11

上面例子中,只要用户输入的文件不存在,continue 命令就会生效,直接进入下一轮循环(让用户重新输入文件名),不再执行后面的打印语句。

# select 结构

select 结构主要用来生成简单的菜单。它的语法与 for...in 循环基本一致。

select name
[in list]
do
  commands
done
1
2
3
4
5

Bash 会对 select 依次进行下面的处理。

  1. select 生成一个菜单,内容是列表 list 的每一项,并且每一项前面还有一个数字编号。
  2. Bash 提示用户选择一项,输入它的编号。
  3. 用户输入以后,Bash 会将该项的内容存在变量 name,该项的编号存入环境变量 REPLY。如果用户没有输入,就按回车键,Bash 会重新输出菜单,让用户选择。
  4. 执行命令体 commands
  5. 执行结束后,回到第一步,重复这个过程。

下面是一个例子。

#!/bin/bash
# select.sh

select brand in Samsung Sony iphone symphony Walton
do
  echo "You have chosen $brand"
done
1
2
3
4
5
6
7

执行上面的脚本,Bash 会输出一个品牌的列表,让用户选择。

$ ./select.sh
1) Samsung
2) Sony
3) iphone
4) symphony
5) Walton
#?
1
2
3
4
5
6
7

如果用户没有输入编号,直接按回车键。Bash 就会重新输出一遍这个菜单,直到用户按下 Ctrl + c,退出执行。

select 可以与 case 结合,针对不同项,执行不同的命令。

#!/bin/bash

echo "Which Operating System do you like?"

select os in Ubuntu LinuxMint Windows8 Windows7 WindowsXP
do
  case $os in
    "Ubuntu"|"LinuxMint")
      echo "I also use $os."
    ;;
    "Windows8" | "Windows10" | "WindowsXP")
      echo "Why don't you try Linux?"
    ;;
    *)
      echo "Invalid entry."
      break
    ;;
  esac
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

上面例子中,case 针对用户选择的不同项,执行不同的命令。

# 参考链接