循环命令

for 命令

基本用法

for 可以遍历一系列值,每次迭代都使用其中的一个值来执行一组命令。格式如下:

for var in list
do
    commands
done

比如:

$ cat test12
#!/bin/bash

for test in A B C D
do
    echo $test
done
echo $test
$ /bin/bash test12
A
B
C
D
D

可以看出执行完后,$test 变量保持了其值,并且在 for 循环外跟其他变量一样使用。

for 循环假定每个值都是空格分隔的,如果某个值中包含空格,我们需要用双引号将它括起来:

$ cat test13
#!/bin/bash

for test in 'A B' "C D"
do
    echo $test
done
$ /bin/bash test13
A B
C D

我们也可以从变量读取列表:

#!/bin/bash

list="Xi'an Guangdong Hunan"
list=$list" Connecticut"
for state in $list
do
    echo "Have you ever visited $state"
done

我们也可以从命令读取值:

$ cat test14
#!/bin/bash

file="states"

for state in $(cat $file)
do
    echo "Visit beautiful $state"
done
$ cat states
Beijing
Guangdong
Xi'an
Hubei
$ /bin/bash test14
Visit beautiful Beijing
Visit beautiful Guangdong
Visit beautiful Xi'an
Visit beautiful Hubei

指定分隔符

注意到 states 中每一行都有一个值,并不是按空格分开的,而 for 每次遍历了一行,说明回车和空格一样也能分隔不同值。这类字符叫 内部字段分隔符(internal field separator),IFS环境变量定义了 bash shell 用作字段分隔符的一系列字符,默认情况下包括:

  • 空格
  • 制表符
  • 换行符

我们可以通过修改 IFS 来指定字段分隔符,比如:

$ cat test15
#!/bin/bash

IFS=$'\n'
for test in A B \n C D
do
    echo $test
done
$ /bin/bash test15
A
B

C
D

如果要用多个 IFS,只要把它们串起来即可:

$ cat test16
#!/bin/bash

IFS="\n ;\""
for test in A\;B\"C\nD
do
    echo $test
done
$ /bin/bash test16
A B C D

用通配符读取目录

我们可以用 for 来遍历目录中的文件。进行此操作时,必须在文件名或路径名中使用通配符。它会强制 shell 使用文件扩展匹配 。

$ cat file17
#!/bin/bash

for file in /home/pi/*
do
    if [ -d "$file" ]
    then
        echo "$file is a directory"
    elif [ -f "$file" ]
    then
        echo "$file is a file"
    fi
done

$ /bin/bash file17
/home/pi/blog is a directory
/home/pi/code is a directory
/home/pi/newfile is a file

for 命令会遍历/home/rich/test/* 输出的结果。该代码用test 命令测试了每个条目(使用方括号方法),以查看它是目录(通过-d 参数)还是文件(通过-f 参数)

注意我们用双引号括住了文件名("$file"),如果不这么做,遇到含有空格的目录名或文件名时就会有错误产生。

C 风格的 for

bash 中 C 语言风格的 for 循环的基本格式如下:

for (( variable assignment ; condition ; iteration process ))
do
    
done

比如:

#!/bin/bash

for (( a = 1 ; a < 10 ; a++ ))
do
    echo $a
done

C 语言风格的 for 命令也允许为迭代使用多个变量。循环会单独处理每个变量,你可以为每个变量定义不同的迭代过程。尽管可以使用多个变量,但你只能在for 循环中定义一种条件。

#!/bin/bash
# multiple variables

for (( a=1, b=10; a <= 10; a++, b-- ))
do
    echo "$a - $b"
done

while 命令

while 的基本格式是:

while test command
do
    other commands
done

比如:

#!/bin/bash

var1=10
while [ $var1 -gt 0 ]
do
    echo $var1
    var1=$[ $var1 - 1 ]
done

我们也可以使用多个测试条件:

$ cat test11
#!/bin/bash
# testing a multicommand while loop

var1=10

while echo $var1
       [ $var1 -ge 0 ]
do
    echo "This is inside the loop"
    var1=$[ $var1 - 1 ]
done

在含有多个命令的while 语句中,在每次迭代中所有的测试命令都会被执行,包括测试命令失败的最后一次迭代。要留心这种用法。另一处要留意的是该如何指定多个测试命令。注意,每个测试命令都出现在单独的一行上。

until 命令

until 命令的格式如下:

until test commands
do
    other commands
done

和 while 命令类似, until 命令中可以放入多个测试命令。只有最后一个命令的退出状态码决定了 bash shell 是否执行已定义的 other commands。

$ cat test18
#!/bin/bash

var1=5

until [ $var1 -eq 1 ]
    [ $var1 -eq 0 ]
do
    echo $var1
    var1=$[ $var1 - 1 ]
done
$ /bin/bash file18
5
4
3
2
1

嵌套循环

循环语句可以在循环内使用任意类型的命令,包括其他循环命令。比如:

#!/bin/bash
# placing a for loop inside a while loop

var1=5

while [ $var1 -ge 0 ]
do
    echo "Outer loop: $var1"
    for (( var2 = 1; $var2 < 3; var2++ ))
    do
       var3=$[ $var1 * $var2 ]
       echo "  Inner loop: $var1 * $var2 = $var3"
    done
    var1=$[ $var1 - 1 ]
done

循环处理文件

我们可以利用嵌套循环提取文件每一行的每个字段:

#!/bin/bash

IFS.OLD=$IFS
IFS=$'\n'
for entry in $(cat /etc/passwd)
do
    echo "Values in $entry -"
    IFS=:
    for value in $entry
    do
        echo "   $value"
    done
done

得到的输出类似于:

Values in rich:x:501:501:Rich Blum:/home/rich:/bin/bash -
   rich
   x
    501
    501
    Rich Blum
    /home/rich
    /bin/bash

控制循环

我们可以通过 continuebreak 来跳过当次或整个循环(和 C/C++/Python/Java 一样)

处理循环的输出

我们可以对循环的输出使用管道或进行重定向,只需要在 done 后面添加重定向符号即可。

for file in /home/pi/*
 do
   if [ -d "$file" ]
   then
      echo "$file is a directory"
   elif
      echo "$file is a file"
   fi
done > output.txt