循环命令: for, while, until

作者:jicanmeng

时间:2016年05月13日


  1. for命令
  2. c语言风格的for命令
  3. while命令
  4. until命令
  5. break和continue命令
  6. 循环输出的重定向
  7. 实用例子

1. for命令

for命令的格式如下:

for var in list
do
    commands
done

一般情况下,list中的各项使用空格分开。每项都赋值给var变量。如果list中的项包含空格,我们可以用转义字符对空格进行转义,或用双引号或单引号。举例如下:

[jicanmeng@andy tmp]$ cat 1-for.sh
                        #!/bin/bash

                        for city in beijing shanghai shenzhen
                        do
	                            echo "visit beautiful $city"
                        done

                        for city in bei\ jing "shang hai" 'shen zhen'
                        do
	                            echo "Visit beautiful $city"
                        done
                    [jicanmeng@andy tmp]$ bash 1-for.sh
                        visit beautiful beijing
                        visit beautiful shanghai
                        visit beautiful shenzhen
                        Visit beautiful bei jing
                        Visit beautiful shang hai
                        Visit beautiful shen zhen
                    [jicanmeng@andy tmp]$ 

list的内容也可以通过读取文件来获得。例如下面的例子:

[jicanmeng@andy tmp]$ cat city.txt
                        bei jing
                        shang hai
                        shen zhen
                    [jicanmeng@andy tmp]$ cat 2-for.sh
                        #!/bin/bash

                        for city in `cat city.txt`
                        do
	                            echo "visit beautiful $city"
                        done
                    [jicanmeng@andy tmp]$ bash 2-for.sh
                        visit beautiful bei
                        visit beautiful jing
                        visit beautiful shang
                        visit beautiful hai
                        visit beautiful shen
                        visit beautiful zhen
                    [jicanmeng@andy tmp]$ 

这和我们期望的不一样。我们期望一行一行打印,而实际结果是一行中的空格也作为了分隔符,分隔符前后的内容分别赋值给了city。这个问题的原因是一个特殊的环境变量IFS,称为内部字段分隔符(internal filed separator)。IFS环境变量保存了bash shell用作字段分隔符的一系列字符。默认情况下,bash shell会将下列字符当作字段分隔符:

如果bash shell在数据列表中看到了这些字符中的任意一个,它会假定你在数据列表中开始了一个新的数据段。在读取文件时,如果文件中包含空格,可能就会比较麻烦,例如上面的例子。

要解决这个问题,我们可以在shell脚本中临时更改一些IFS环境变量的值,处理完数据后再把IFS的原值更改回来。例如下面的例子:

[jicanmeng@andy tmp]$ cat city.txt
                        bei jing
                        shang hai
                        shen zhen
                    [jicanmeng@andy tmp]$ cat 3-for.sh
                        #!/bin/bash

                        IFSOLD=$IFS
                        IFS=$'\n'
                        for city in `cat city.txt`
                        do
	                            echo "visit beautiful $city"
                        done
                        IFS=$IFSOLD
                    [jicanmeng@andy tmp]$ bash 3-for.sh
                        visit beautiful bei jing
                        visit beautiful shang hai
                        visit beautiful shen zhen
                    [jicanmeng@andy tmp]$ 

IFS=$'\n'表示IFS环境变量中只包含换行符。

如果你要指定多个IFS字符,只要将它们在赋值行串起来就行。例如IFS=$'\n:;"'这个赋值语句会将换行符、冒号、分号和双引号作为字段分隔符。

for命令在一种情况下也非常常见。那就是遍历一个目录下面的所有文件的目录和文件名称。例如:

[jicanmeng@andy tmp]$ cat 4-for.sh
                        #!/bin/bash

                        for file in /home/jicanmeng/*
                        do
	                            if [ -d "$file" ]
	                            then
		                                echo "$file is a directory"
	                            elif [ -f "$file" ]
	                            then
		                                echo "$file is a file"
	                            fi
                        done
                    [jicanmeng@andy tmp]$ bash 4-for.sh
                        /home/jicanmeng/2015年年终总结.txt is a file
                        /home/jicanmeng/Downloads is a directory
                        /home/jicanmeng/lib is a directory
                        /home/jicanmeng/pictures is a directory
                        /home/jicanmeng/Public is a directory
                        /home/jicanmeng/softwares is a directory
                        /home/jicanmeng/VirtualBox VMs is a directory
                        /home/jicanmeng/work is a directory
                    [jicanmeng@andy tmp]$ 

这里要提醒一下,测试文件是文件还是目录的时候,注意添加双引号。如果不这么做,那么遇到包含有空格的目录或文件名时会提示参数过多的错误。

2. c语言风格的for命令

bash shell也支持另一种形式的for循环,看起来和c语言风格的for循环非常类似。格式如下:

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

注意,有一些事情没有遵循标准的bash shell的for命令:1. 给变量赋值等号前后可以有空格; 2. 条件中的变量不以$开头; 3. 迭代过程的算式未用expr命令格式。举例如下:

[jicanmeng@andy tmp]$ cat 5-for.sh
                    #!/bin/bash

                    for (( i = 1; i < 5; i++ ))
                    do
                            echo "The next number is $i"
                    done

                    for (( a=1,b=5; a<5; a++,b-- ))
                    do
                            echo "$a - $b"
                    done
                [jicanmeng@andy tmp]$ bash 5-for.sh
                    The next number is 1
                    The next number is 2
                    The next number is 3
                    The next number is 4
                    1 - 5
                    2 - 4
                    3 - 3
                    4 - 2
                [jicanmeng@andy tmp]$ 

3. while命令

while命令的基本格式:

while command
do
    commands
done

两点需要注意:

  1. while后面的command和if-then语句中if后面的command相同,也可以用test和[];
  2. while后面的command可以是多个命令。一般情况下,每个命令都在单独的一行上。如果两个命令不在同一行上,应该使用;&&连接起来。特别注意的是,while的条件是否满足,取决于while命令中的最后一条命令的退出状态码。

[jicanmeng@andy tmp]$ cat 6-while.sh
                    #!/bin/bash

                    var1=3
                    while [ $var1 -gt 0 ]
                    do
                            echo $var1
                            var1=$[ var1 -1 ]
                    done
                    echo "first while end"

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

                    var1=3
                    while test $var1 -ge 0 && echo $var1
                    do
                            echo "This is inside the loop"
                            var1=$[$var1-1]
                    done
                [jicanmeng@andy tmp]$ bash 6-while.sh
                    3
                    2
                    1
                    first while end
                    3
                    This is inside the loop
                    2
                    This is inside the loop
                    1
                    This is inside the loop
                    0
                    This is inside the loop
                    -1
                    second while end
                    3
                    This is inside the loop
                    2
                    This is inside the loop
                    1
                    This is inside the loop
                    0
                    This is inside the loop
                [jicanmeng@andy tmp]$ 

4. until命令

until命令的基本格式:

until command
do
    commands
done

until命令和while命令的格式和参数完全相同,只有一点需要注意:while后面的命令退出状态码为0时,才会执行do后面的命令;而until后面的命令退出码不为0时,才会执行do后面的命令。

5. break和continue命令

和c语言一样,bash shell也支持在循环中使用break命令和continue命令。它们都可以加一个参数:break n。默认情况下,n为1;如果你将n设为2,break就会停止上一级的外部循环。这在嵌套循环中非常有用。举例如下:

[jicanmeng@andy tmp]$ cat 7-break.sh
                    #!/bin/bash

for a in 1 2 3 4
do
	    echo "outer loop: $a"
	    for (( b = 1; b < 100; b++ ))
	    do
		        if [ $b -gt 4 ]
		        then
			            break 2
		        fi
		        echo "    inner loop: $b"
	    done
done
                [jicanmeng@andy tmp]$ bash 7-break.sh
                    outer loop: 1
                        inner loop: 1
                        inner loop: 2
                        inner loop: 3
                        inner loop: 4
                [jicanmeng@andy tmp]$ 

6. 循环输出的重定向

无论是使用for,还是使用while,还是使用until,都可以在done后面加上重定向符号,将循环的输出重定向到文件中。或在done后面添加管道符号|,将循环的输出传递给另外一个程序。举例如下:

[jicanmeng@andy tmp]$ cat 8-redirection.sh
                    #!/bin/bash

                    for file in /home/jicanmeng/*
                    do
                            if [ -d "$file" ]
                            then
	                                echo "$file is a directory"
                            elif [ -f "$file" ]
                            then
	                                echo "$file is a file"
                            fi
                    done > output.txt
                [jicanmeng@andy tmp]$ bash 8-redirection.sh
                [jicanmeng@andy tmp]$ cat output.txt
                    /home/jicanmeng/2015年年终总结.txt is a file
                    /home/jicanmeng/Downloads is a directory
                    /home/jicanmeng/lib is a directory
                    /home/jicanmeng/pictures is a directory
                    /home/jicanmeng/Public is a directory
                    /home/jicanmeng/softwares is a directory
                    /home/jicanmeng/VirtualBox VMs is a directory
                    /home/jicanmeng/work is a directory
                [jicanmeng@andy tmp]$ 

7. 实用例子

  1. 取出/etc/passwd文件中的每行数据,将这一行的每列都打印出来。
  2. [jicanmeng@andy tmp]$ cat 9-example.sh
                        #!/bin/bash
    
                        IFSOLD=$IFS
                        IFS=$'\n'
                        for line in `cat /etc/passwd`
                        do
    	                        echo "values in $line -"
    	                        IFS=:
    	                        for column in $line
    	                        do
    		                            echo "    $column"
    	                        done
                        done
                    [jicanmeng@andy tmp]$ bash 9-example
                    [jicanmeng@andy tmp]$ cat output.txt
                        values in root:x:0:0:root:/root:/bin/bash -
                            root
                            x
                            0
                            0
                            root
                            /root
                            /bin/bash
                        ...
                    [jicanmeng@andy tmp]$ 

参考资料

  1. Lnux命令行与shell脚本编程大全
  2. 鸟哥的linux私房菜:
    http://linux.vbird.org/linux_basic/0340bashshell-scripts.php