Linux命令行与shell编程 第12章更多的结构化命令

本章内容

  •     用 for语句来循环
  •     用until语句来迭代
  •     使用while语句
  •     循环
  •     重定向循环的输出

在上章里,你了解了如何通过检查命令的输出和变量的值来改变shell脚本程序的流程。本章中,我们会继续介绍能够控制shell脚本流程的结构化命令。你会了解如何重复一些过程和命令,也就是循环执行一组命令直至达到了某个特定条件。本章将会讨论和演示bash shell的循环命令for、while和until。

12.1 for命令

重复执行一系列命令在编程中很常见。通常你需要重复一组命令直至达到某个特定条件,比如处理某个目录下的所有文件、系统上所有用户或某个文本文件中的所有行。

bash shell提供了for命令,允许你创建一个遍历一系列值的循环。每个迭代都通过一个该系列中的值执行一组预定义的命令。下面是bash shell中for命令的基本格式:

for var in list

do

    commands

done

在list 参数中,你提供了迭代中要用的一系列值。你可以通过几种不同的途径来指定列表中的值。

在每个迭代中,变量var会包含列表中的当前值。第一个迭代会使用列表中的第一个值,第二个迭代使用第二个值,以此类推,直到列表中的所有值都过一遍。

在do和done语句之间输入的命令可以是一条或多条标准的bash shell命令。在这些命令中,$var变量包含着这些迭代对就的当前那个列表中的值。

说明 只要你愿意,也可以将do语句和for语句放在同一行,但必须用分号将其同列表中的值分开:for var in list; do

12.1.1 读取列表中听值

for命令最基本的用法就是遍历for命令自身中定义的一系值:

[jingpan@localhost ch12]$ cat test1
#!/bin/bash
#basic for command
for test in Alabama Alaska Arizona Arkansas California Colorado
do
        echo The next state is $test
done

[jingpan@localhost ch12]$ ./test1
The next state is Alabama
The next state is Alaska
The next state is Arizona
The next state is Arkansas
The next state is California
The next state is Colorado

每次for命令遍历提供的值列表时,它会将列表中的下个值赋给$test变量。$test变量像for命令语句中的任何其他脚本变量一样使用。在最后一次迭代后,$test变量的值会在shell脚本 的剩余部分一直操持有效。它会一直保持最后一个迭代的值(除非你修改了它):

[jingpan@localhost ch12]$ cat test1b
#!/bin/bash
#basic for command
for test in Alabama Alaska Arizona Arkansas California Colorado
do
        echo The next state is $test
done
echo "The last state we visited was $test"
test=Connecticut
echo "Wait,now we're visiting $test"
[jingpan@localhost ch12]$ ./test1b
The next state is Alabama
The next state is Alaska
The next state is Arizona
The next state is Arkansas
The next state is California
The next state is Colorado  
The last state we visited was Colorado
Wait,now we're visiting Connecticut

$test变量保持了它的值,也允许我们修改它的值并在for命令循环之外跟其他变量一样使用。

12.1.2 读取列表中的复杂值

事情并不像for循环看上去那么简单。有时你会遇到难处理的数据。下面是个给shell脚本程序员带来麻烦的经典例子:

[jingpan@localhost ch12]$ cat badtest1 
#!/bin/bash
#another example of how not to use the for command

for test in I don't know if this'll work
do
        echo "wrod:$test"
done

[jingpan@localhost ch12]$ ./badtest1 
wrod:I
wrod:dont know if thisll
wrod:work

真麻烦。shell看到了列表中的单引号并尝试使用它们来定义一个单独的数据值,这个过程一团混乱。

有两种办法解决这个问题:

        使用转义字符(反斜线)来将单引号转义;

        使用双引号来定义用到单引号的值。

每个解决方法都非那么神奇,但每个都能解决这个问题:

[jingpan@localhost ch12]$ cat test2
#!/bin/bash
#another example of how not to use the for command
for test in I don\'t know if "this'll" wrok
do echo "world:$test"
done
[jingpan@localhost ch12]$ ./test2
world:I
world:don't
world:know
world:if
world:this'll
world:wrok

在第一个有问题的值上,添加了反斜线字符来转义don't值中的单引号。在第二个有问题的值上,将this'll 值用双引号圈起来。两种方法者能正常工作,辨别出这个值。

你可能遇到的另一个问题是有多个词的值。记住,for循环假定每个值都是用空格分割的。

如果有包含空格的数据的数据值,你可以会遭到另一个问题:

[jingpan@localhost ch12]$ cat badtest2
#!/bin/bash
# anather example of now not to use the for command
for test in Nevada New Hampshire New Mexico New York North Carolian
do
        echo "New going to $test"
done

[jingpan@localhost ch12]$ ./badtest2
New going to Nevada
New going to New
New going to Hampshire
New going to New
New going to Mexico
New going to New
New going to York
New going to North
New going to Carolian

这不是我们想要的结果。for命令用空格来划分列表中的每个值。如果在单独的数据值中有空格,那么你必须双引引号来将这些值圈起来:

[jingpan@localhost ch12]$ cat test3
#!/bin/bash
# an example of how to properl define values
for test in Nevada "New Hampshire" "New Mexico" "New York"
do
        echo "Now going to $test"
done

[jingpan@localhost ch12]$ ./test3
Now going to Nevada
Now going to New Hampshire
Now going to New Mexico
Now going to New York

现在for命令可以正确的区分不同值了。还有,注意当你在某个值两边使用双引号时,shell不会将双引号当月成值的一部分。

12.1.3 从变量读取列表

通常shell脚本遇到的情况是,你将一系列值都集中存储在了一个变量中,然后需要遍历整个列表。你也可以通过for命令完成这个:       

[jingpan@localhost ch12]$ cat test4
#!/bin/bash
#using a variable to hold the list
list="Alabama Alaska Arizona Arkansas Colorado"
list=$list" Ccnnecticut"
for state in $list
do
        echo "Have you ever visited $state?"
done

[jingpan@localhost ch12]$ ./test4
Have you ever visited Alabama?
Have you ever visited Alaska?
Have you ever visited Arizona?
Have you ever visited Arkansas?
Have you ever visited Colorado?
Have you ever visited Ccnnecticut?

$list变量包含了给迭代用的标准文本值列表。注意,代码还是用了另一个赋值语句来向$list变量包含的已有列表添加了一个值。这是向变量中存储的已有文本字符串尾部添加文本的一个常用方法。

12.1.4  从命令读取值

生成列表中要用的值的另外一个途径就是使用命令的输出。你可以用反引号来执行任何能产生输出的命令,然后在for命令中使用该命令的输出:

[jingpan@localhost ch12]$ cat test5
#!/bin/bash
#reading values from a file
file="states"
for state in `cat $file`
do
        echo "Visit beautifu $state"
done

[jingpan@localhost ch12]$ cat states 
Alabama
Alaska
Arizona
Arikansas
Colorado
Connecticut
Delaware
Florida
Georgia
[jingpan@localhost ch12]$ ./test5
Visit beautifu Alabama
Visit beautifu Alaska
Visit beautifu Arizona
Visit beautifu Arikansas
Visit beautifu Colorado
Visit beautifu Connecticut
Visit beautifu Delaware
Visit beautifu Florida
Visit beautifu Georgia

这个例子在反引号中使用了cat命令来输出文件states的内容。你会注意到states文件每一行有一个州,而不是通过空格分割的。for命令仍然以每次一行的方式遍历了cat命令的输出,假定每个州都是在单独的一行上。但这并没有解决数据中有空格的问题。如果你列出了一个名字中有空格的州,for命令仍然会将每个单词当作单独的值。这是有原因的,下一节我们将会了解。

说明 test5的代码范例将文件名赋给变量,只用了文件名而不是路径。这要求文件和脚本位于同一个目录中。如果不是的话,你需要使用全路径名(不管是绝对路径还是相对路径)来引用文件位置。

12.1.5 更改字段分隔符

这个问题的原因是特殊的环境变量IFS,称为内部字段分隔符(internal field separator)。IFS环境变量定义了bash shell用作字符分隔符的一素列字符。默认情况下,bash shell会将下列字符当作字段分隔符:

  •     空格;
  •     制表符;
  •     换行符。

如果bash shell在数据中看到了这些字符中的任意一个,它就会假定你在列表中开始了一个新的数据段。在处理可能含有空的数据(比如文件名),这会非常麻烦,如你在上一个脚本示例中看到的。

要解决这个问题,你可以在shell脚本中临时更改IFS环境变量的值来限制一下被bash shell当作字段分隔的字符。但这种方式有点奇怪。比如,如果你修改 IFS的值使其只能识别换行符,你必须这么做:

IFS=$'\n'

将这个语句加入到脚本中,告诉bash shell在数据值中忽略空格和制表符。对前一个脚本使用这种方法,将获得如下输出:

[jingpan@localhost ch12]$ cat test5b
#!/bin/bash
#reading values from a file
file="states"
IFS=$'\n'
for state in `cat $file`
do
        echo "Visit beautifu $state"
done

[jingpan@localhost ch12]$ ./test5b
Visit beautifu Alabama
Visit beautifu Alaska
Visit beautifu Arizona
Visit beautifu Arikansas
Visit beautifu Colorado
Visit beautifu Connecticut
Visit beautifu Delaware
Visit beautifu Florida
Visit beautifu Georgia
Visit beautifu New York
Visit beautifu New Hampshire
Visit beautifu North Carolina

现在shel脚本能够使用列表中含有空格的值了。

警告    在处理长脚本时,可能在一个地方需要修改IFS的值,然后忘掉它了并在脚本中其他地方以为还是默认的值。一个可参考的简单实践是在改变IFS之前保存原来的IFS值,之后再恢复它。

这种技术可以这样编程:

    IFS.OLD=$IFS

    IFS=$'\n'

    <use the new IFS value in code>

    IFS=$IFS.OLD

这会为脚本中后面的操作保证IFS的值恢复到了默认值。

还有其他一些IFS环境变量的绝妙用法。假定你要遍历一个文件中用冒号分割的值(比如在/etc/passwd文件中)。你要做的就是将IFS的值设为冒号:

IFS=:

如果你要指定多个IFS字符,只要将它们在赋值行串起来就行:

IFS=$'\n:;'

这个赋值会将换行符、冒号、分号和双引号作为字段分隔符。如何使用IFS字符解析数据没有任何限制。

12.1.6 用通配符读取目录

最后,你可以用for命令来自动遍历满是文件的目录。进行此操作时,你必须在文件名或路径名中使用通配符,它会强制shell使用文件扩展匹配(file globbing)。文件扩展匹配是生成匹配指定的通配符的文件名或路径名的过程。

这个特性在处理目录中的文件而你不知道所有的文件名时非常好用:

[jingpan@localhost ch12]$ cat test6
#!/bin/bash
# iterate through all the files in a directory

for file in /home/jingpan/linux_shell/ch12/*
do
        if [ -d "$file" ]
        then
                echo "$file is a directory"
        elif [ -f "$file" ]
        then
                echo "$file is a file"
        fi
done
[jingpan@localhost ch12]$ ./test6
/home/jingpan/linux_shell/ch12/aa is a file
/home/jingpan/linux_shell/ch12/badtest1 is a file
/home/jingpan/linux_shell/ch12/badtest2 is a file
/home/jingpan/linux_shell/ch12/dir1 is a directory
/home/jingpan/linux_shell/ch12/newdir is a directory
/home/jingpan/linux_shell/ch12/states is a file
/home/jingpan/linux_shell/ch12/test1 is a file
/home/jingpan/linux_shell/ch12/test1b is a file
/home/jingpan/linux_shell/ch12/test2 is a file
/home/jingpan/linux_shell/ch12/test3 is a file
/home/jingpan/linux_shell/ch12/test4 is a file
/home/jingpan/linux_shell/ch12/test5 is a file
/home/jingpan/linux_shell/ch12/test5b is a file
/home/jingpan/linux_shell/ch12/test6 is a file
/home/jingpan/linux_shell/ch12/testdir is a directory
[jingpan@localhost ch12]$ 

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

注意,在这个例子中,我们和if语句里的测试处理得有些不同:

if [ -d "$file" ]

在Linux中,目录名和文件名中包含空格当然是合法的。要容纳这种值,你应该将$file变量用双引号圈起来。如果不这么做,遇到含有空格的目录名或文件名时会有错误产生:

./test6b: 第 6 行:[: /home/jingpan/linux_shell/ch12/aa: 期待二元表达式
./test6b: 第 9 行:[: /home/jingpan/linux_shell/ch12/aa: 期待二元表达式

在test命令中,bash shell会将额外的单词当作参数,造成错误。

你也可以在for命令中通过列出一系列的目录通配符来将目录查找方法和列表方法合并合并同一个for语句:

[jingpan@localhost ch12]$ cat test7
#!/bin/bash
# iterating through multiple directories
for file in /home/jingpan/.b* /home/rich/badtest
do
        if [ -d "$file" ]
        then
                echo "$file is a directory"
        elif [ -f "$file" ]
        then
                echo "$file is a file"
        else
                echo "$file dooesn't exist"
        fi
done
[jingpan@localhost ch12]$ ./test7
/home/jingpan/.bash_history is a file
/home/jingpan/.bash_logout is a file
/home/jingpan/.bash_profile is a file
/home/jingpan/.bashrc is a file
/home/rich/badtest dooesn't exist
[jingpan@localhost ch12]$ 

for语句首先使用了文件扩展匹配来遍历通配符生成的文件列表,然后它会遍历列表中的下一个文件。你可以将任意多的通配符放进表中。

警告 注意,你可以在列表数据中放入任何东西。即使文件或目录不存在,for语句也会尝试处理你放到列表中的任何东西。在处理文件或目录时,这可能会是个问题。你也不知道你正在尝试遍历一个并不存在的目录:在处理之前测试一下文件或目录总是好的。

12.2 C语言风格的for命令

如果你用C语言编过程,你可能会对bash shell使用for命令的方式有点惊奇。在C语言中,for循环通常定义一个变量,然后这个变量会在每次迭代时自动改变。通常,程序员会将这个变量用作计数器,并在每次迭代中让计算器增一或减一。bash的for命令也提供了这个功能。本节将会告诉你如何在bash shell脚本中使用C语言风格的for命令。

12.2.1 C语言的for命令

C语言的for命令有一个用来指明变量的特殊方法、一个必须保持成立才能继续迭代的条件,以及另一个为每个迭代改变变量的方法。当指定的条件不成立时,for循环就会停止。条件等式通过标准的数学符号定义。比如,考虑面面的C语言代码:

#include <stdio.h>
int main()
{
        int i = 0;
        for(i=0; i< 10; i++)
        {
                printf("The next number is %d\n",i);
        }
        return 0;
}

这段代码产生了一个简单的迭代循环,其中变量i作为计数器。第一部分将一个默认值赋给该变量。中间的部分定义了循环重复的条件。当定义的条件不成立时,for循环就停止迭代了。

最后一部分定义了迭代的过程。在每次迭代之后,最后一部分中定义的表达式会被执行。在本例中,i变量会在每次迭代后增一。

bash shell也支持一个版本的for循环,看起来跟C语言风格的for循环类似,虽然它也有一些细微的不同,包括一些会叫shell脚本程序员困惑的东西。这是bash中C语言风格的for循环的基本格式:

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

C语言风格的for循环的格式可能对bash shell脚本程序员有点困惑,由于它使用了C语言风格的变量引用方式而不是shell风格的变量引用方式。C语言风格的for命令看起来如下:

for (( a = 1; a < 10; a++))

注意,有一些事情没有遵循标准的bash shell for命令:

    给变量赋值可以有空格;

    条件中的变量不以美元符开关;

    迭代过程的算式未用expr命令格式。

shell开发人员创建了这种格式以更贴切地模仿C语言风格的for命令。这对C语言语言员来说很好,但也可能将即使是专家级的shell程序弄得一头雾水。在脚本中使用C语言风格的for循环时要小心。

这里有个在bash shell 程序中使用C语言风格的for命令的例子:

[root@localhost ch12]# cat test8
#!/bin/bash
#testing the C-ctyle for loop
for (( i=1; i <= 10; i++))
do
        echo "The next number is $i"
done

[root@localhost ch12]# ./test8
The next number is 1
The next number is 2
The next number is 3
The next number is 4
The next number is 5
The next number is 6
The next number is 7
The next number is 8
The next number is 9
The next number is 10

for循环通过定义的变量(本例中是字母i)遍历了这些命令。在每个迭代中,$i变量包含了for循环中赋给的值。在每次迭代后,循环的迭代过程会作用在变量上,在本例中,变量增一。

12.2.2 使用多个变量

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

[root@localhost ch12]# cat test9
#!/bin/bash
#multiple variables

for(( a=1,b=10; a<=10; a++,b--))
do 
        echo "$a - $b"
done
[root@localhost ch12]# ./test9
1 - 10
2 - 9
3 - 8
4 - 7
5 - 6
6 - 5
7 - 4
8 - 3
9 - 2
10 - 1

变量a和b每个都用不同的值来初始化并且定义了不同的迭代过程,循环每个迭代中增加变量a的同时,减小了变量b。

12.3 while命令

while test command

do

    other commands

done

while 命令中定义的test命令和if-then语句(参见第11章)中定义的是一样的格式。和if-then语句中一样,你可以使用任何普通的bash shell命令,或者用test命令作为条件,比如变量值。

while命令的关键是,指定的test命令的退出状态码必须随着循环中运行的命令改变。如果退出状态码从来不变,那while循环将会一直不停地循环。

最常见的test命令的用法是,用方括号来查看循环命令中用到的shell变量的值:

[root@localhost ch12]# cat test10
#!/bin/bash
#while command test
var1=10
while [ $var1 -gt 0 ]
do
        echo $var1
        var1=$[ $var1 -1 ]
done
[root@localhost ch12]# ./test10
10
9
8
7
6
5
4
3
2
1

while 命令定义了每次迭代时检查的测试条件:

while [ $var1 -gt 0 ]

只有测试条件成立,while命令才会继续遍历执行定义好的命令。在这些命令中,测试条件中用到的变量必须被修改,否则你就进入了一个无限循环。在本便中,我们用shell算术来将变量值减一:

var1=$[ $var1 -1]

while循环会在测试条件不再成立时停止。

12.3.2 使用多个测试命令

在极少数情况下,while命令允许你在while语句行定义多个测试命令。只有最后一个测试命令的退出码会被用来决定什么时候结束循环。如果你不够小心,这可能会导致一些有意思的结果。下面的例子将会说明:

[root@localhost ch12]# cat test11
#!/bin/bash
#testing a multicommand while loop
var1=10
while echo $var1
        [ $var1 -ge 0 ]
do
        echo "Thie is inside the loop"
        var1=$[ $var1 -1 ]
done

[root@localhost ch12]# ./test11
10
Thie is inside the loop
9
Thie is inside the loop
8
Thie is inside the loop
7
Thie is inside the loop
6
Thie is inside the loop
5
Thie is inside the loop
4
Thie is inside the loop
3
Thie is inside the loop
2
Thie is inside the loop
1
Thie is inside the loop
0
Thie is inside the loop
-1

注意本例中做了什么。在while语句中定义了两个测试命令:

while echo $var1

    [ $var1 -ge 0 ]

第一个测试会简单地显示var1变量的当前值。第二个测试用方括号来决定var1变量的值。在循环内部,echo语句会显示一条简单的消息,说明循环执行了。注意当你运行本例时输出是如何结尾的:

This is inside the loop

-1

while循环会在var1变量等于零时执行echo语句,然后将var1变量的值减一。下一步,测试命令会为下个迭代执行。echo测试命令被执行了,显示了var变量的值(现在比0小)。shell直到执行test测试命令while循环才会停止。

这说明在含有多个命令的while语句中,在每次迭代中所有的测试命令都会执行,包括最后一个测试命令不成立的最后那次循环。要小心这种用法。另一个要小心的是你如何指定多个测试命令。注意每个测试命令都是在单独的一行上。

12.4 until命令

until命令和while命令工作的方式完全相反。until命令要求你指定一个通常输出非零退出状态码的测试命令。只有测试命令的退出状态码非零,bash shell才会指定循环中列出的那些命令。一旦测试命令返回了退出状态码,循环就结束了。

如你所期望的,until命令的格式如下:

until test commands

do

    other comands

done

类似于while命令,你可以在until命令语句中有多个测试命令。只有最后一个命令的退出状态码决定bash shell是否执行定义好的其他命令。

下面是使用until命令的一个例子:

[root@localhost ch12]# cat test12
#!/bin/bash
#using the until command
var1=100
until [ $var1 -eq 0 ]
do
        echo $var1
        var1=$[ $var1 -25 ]
done

[root@localhost ch12]# ./test12
100
75
50
25

本例中会测试var1变量来决定until循环何时停止。只要变量的值等于0了,until命令就会停止循环了。同while命令一样,在和until命令一起使用多个测试命令时要注意:

[root@localhost ch12]# cat test13
#!/bin/bash
#using the until comand
var1=100

until echo $var1
        [ $var1 -eq 0 ]
do
        echo inside the loop:$var1
        var1=$[ $var1 -25 ]
done
[root@localhost ch12]# ./test13
100
inside the loop:100
75
inside the loop:75
50
inside the loop:50
25
inside the loop:25
0

shell会执行指定的测试命令,只有在最后一个命令成立时停止。

12.5 嵌套循环

循环语句可以在循环内使用任意类型的命令,包括其他循环命令。这种称为嵌套循环(nested loop)。注意,在使用嵌套循环时,你是在迭代中使用迭代,命令运行的次数是乘积关系。不注意这点有可能在脚本中造成问题。

这里有个在for循环中嵌套for循环的简单例子:

[root@localhost ch12]# cat test14
#!/bin/bash
#nesting for loops

for (( a = 1; a <= 3; a++))
do
        echo "Starting loop $a"
        for(( b =1; b <=3; b++))
        do
                echo "  inside loop:$b"
        done
done
[root@localhost ch12]# ./test14
Starting loop 1
  inside loop:1
  inside loop:2
  inside loop:3
Starting loop 2
  inside loop:1
  inside loop:2
  inside loop:3
Starting loop 3
  inside loop:1
  inside loop:2
  inside loop:3

这个被嵌套的循环(也称为内部循环)会在外部循环的每次迭代中遍历一遍历它所有的值。注意,两个循环的do和done没有任何差别。bash shell 知道当第一个done命令执行时是批内部循环而非外部循环。

在混用循环命令时也一样,比如在while循环内部放置一个for循环:

[root@localhost ch12]# cat test15
#!/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
[root@localhost ch12]# ./test15
Outer loop:5
  Inner loop: 5 * 1 = 5
  Inner loop: 5 * 2 = 10
Outer loop:4
  Inner loop: 4 * 1 = 4
  Inner loop: 4 * 2 = 8
Outer loop:3
  Inner loop: 3 * 1 = 3
  Inner loop: 3 * 2 = 6
Outer loop:2
  Inner loop: 2 * 1 = 2
  Inner loop: 2 * 2 = 4
Outer loop:1
  Inner loop: 1 * 1 = 1
  Inner loop: 1 * 2 = 2
Outer loop:0
  Inner loop: 0 * 1 = 0
  Inner loop: 0 * 2 = 0

同样,shell能够区分开内部for循环的do和done命令和外部while循环的do和done命令。

如果你真的想锻炼一下大脑,甚至可以混用until和while循环:

[root@localhost ch12]# cat test16
#!/bin/bash
#using until and while loops
var1=3
until [ $var1 -eq 0 ]
do
        echo "Outer loop: $var1"
        var2=1
        while [ $var2 -lt 5 ]
        do
                var3=`echo "scale=4; $var1 / $var2" | bc`
                echo " Inner loop:$var1 / $var2 = $var3"
                var2=$[ $var2 + 1 ] 
        done
        var1=$[ $var1 -1 ]
done
[root@localhost ch12]# ./test16
Outer loop: 3
 Inner loop:3 / 1 = 3.0000
 Inner loop:3 / 2 = 1.5000
 Inner loop:3 / 3 = 1.0000
 Inner loop:3 / 4 = .7500
Outer loop: 2
 Inner loop:2 / 1 = 2.0000
 Inner loop:2 / 2 = 1.0000
 Inner loop:2 / 3 = .6666
 Inner loop:2 / 4 = .5000
Outer loop: 1
 Inner loop:1 / 1 = 1.0000
 Inner loop:1 / 2 = .5000
 Inner loop:1 / 3 = .3333
 Inner loop:1 / 4 = .2500

外部的until循环以值3开始并继续执行直到等于0。内部while循环会以值1开始并一直执行,只要值小于5.每个循环都必须改变在测试条件中用到的值,否则循环就会无止尽进行下去。

12.6 循环处理文件数据

通常,你必须遍历存储在文件中的数据。这要求结合已经讲过的两种技术:

使用嵌套循环;

修改IFS环境变量。

通过修改IFS环境变量,你就能强制for命令将文件中的每行都当成单独的一个条目来处理,即使数据中有空格也是如此。一旦你从文件中提取出了单独的行。你可能需要再次循环来提取其中的数据。

经典的例子是处理/etc/passwd文件中的数据。这要求你逐行遍历/etc/passwd文件并将IFS变量的值 改变 成冒号,这样你就能分隔开每行中的各个单独的数据段了:

[root@localhost ch12]# cat test17
#!/bin/bash
#changing the IFS value

IFSOLD=$IFS
IFS=$'\n'
for entry in  `cat /etc/passwd`
do
        echo "Values in $entry -"
        IFS=:
        for value in $entry
        do
                echo "  $value"
        done
done
[root@localhost ch12]# ./test17
Values in root:x:0:0:root:/root:/bin/bash -
  root
  x
  0
  0
  root
  /root
  /bin/bash
Values in bin:x:1:1:bin:/bin:/sbin/nologin -
  bin
  x
  1
  1
  bin
  /bin
  /sbin/nologin

这个脚本使用了两个不同的IFS值来解析数据。第一个IFS值解析出/etc/psswd文件中的单独的行。内部fro循环进一步将IFS的值修改为冒号,允许你从/etc/passwd的行中解析出单独的值。

内部循环会解析出/etc/passwd每行中的每个单独的值。这用来处理通常导入电子表格采用的逗号分隔的数据也很方便。

12.7 控制循环

你可能会想,一旦启动了循环,就必须等到循环完成所热的迭代。不是这样的。有几个命令能帮我们控制循环内部情况:

break命令;

continue命令。

每个命令在如何控制循环的执行上有不同的用法。下面几节将会介绍如何使用这些命令来控制循环的执行。

12.7.1 break命令

break命令是退出进行中的循环的一个简单方法。你可以用break命令来退出任意类型的循环。包括while和until循环。

有几种情况你可以使用break命令。本节将会介绍这些方法中的每一种。

1.跳出单个循环

在shell执行break命令时,它会尝试跳出正在处理的循环:

[root@localhost ch12]# cat test17b
#!/bin/bash
#breaking out of a for loop
for var1 in 1 2 3 4 5 6 7 8 9 10
do
        if [ $var1 -eq 5 ]
        then
                break
        fi
        echo "Iteration number:$var1"
done
echo "The for loop is completed"
[root@localhost ch12]# ./test17b
Iteration number:1
Iteration number:2
Iteration number:3
Iteration number:4
The for loop is completed

for循环通常都会遍历一遍列表中指定的所有值。但当满足if-then的条件时,shell会执行break命令,for循环会停止。

这种方法同样适用于while和until循环:

[root@localhost ch12]# cat test18
#!/bin/bash
#breakiing out of a while loop
var1=1
while [ $var1 -lt 10 ]
do
        if [ $var1 -eq 5 ]
        then
                 break
        fi
        echo "Iteration:$var1"
        var1=$[ $var1 +1 ]
done
echo "The while loop is completed"

[root@localhost ch12]# ./test18
Iteration:1
Iteration:2
Iteration:3
Iteration:4
The while loop is completed

while循环会在if-then的条件满足时执行break命令,终止。

2.跳出内部循环

在处理多个循环时,break命令会自动终止你所在最里面的循环:

[root@localhost ch12]# cat test19
#!/bin/bash
#breaking out of an inner loop
for (( a = 1; a < 4; a++ ))
do
        echo "Outer loop:$a"
        for (( b = 1; b < 100; b++ ))
        do
                if [ $b -eq 5 ]
                then
                        break
                fi
                echo "   Inner loop:$b"
        done
done
[root@localhost ch12]# ./test19
Outer loop:1
   Inner loop:1
   Inner loop:2
   Inner loop:3
   Inner loop:4
Outer loop:2
   Inner loop:1
   Inner loop:2
   Inner loop:3
   Inner loop:4
Outer loop:3
   Inner loop:1
   Inner loop:2
   Inner loop:3
   Inner loop:4

内部循环里的for语句指明一直重复直到变量b等于100。但内部循环的if-then语句指明当变量b的值等于5时执行brreak命令。注意,即使内部循环通过break命令终止了,外部循环依然按指定的继续执行。

3.跳出外部循环

有时你在内部循环,但需要停止外部循环。break命令接受单个命令行参数值:

      break n

其中n说明了要跳出的循环层级。默认情况下,n为1,表明跳出的是当前的循环。如果你将n设为2,break命令就会停止下一级的外部循环:

[root@localhost ch12]# cat test20
#!/bin/bash
#breaking out of an outer loop
for (( a = 1; a < 4; a ++))
do
        echo "Outer loop:$a"
        for (( b = 1; b < 100; b++))
        do
                if [ $b -gt 4 ]
                then
                        break 2
                fi
                echo "     Innner loop:$b"
        done
done
[root@localhost ch12]# ./test20
Outer loop:1
     Innner loop:1
     Innner loop:2
     Innner loop:3
     Innner loop:4

注意,当shell执行了break命令时,外部循环停止了。

12.7.2 continue命令

continue命令是提早结束执行循环内部的命令但并不完全终止整个循环的一个途径。它允许你在循环内部设置shell不执行命令的条件。这里有个在for循环中使用continue命令的简单例子:

[root@localhost ch12]# cat test21
#!/bin/bash
#using the continue command

for (( var1 = 1; var1 < 15; var1++))
do
        if [ $var1 -gt 5 ] && [ $var1 -lt 10 ]
        then
                continue
        fi
        echo "Iteration number:$var1"
done
[root@localhost ch12]# ./test21
Iteration number:1
Iteration number:2
Iteration number:3
Iteration number:4
Iteration number:5
Iteration number:10
Iteration number:11
Iteration number:12
Iteration number:13
Iteration number:14

当if-then语句的条件满足时(值大于5而小于10),shell会执行continue命令,跳过循环中的其他命令,但循环会继续。当if-then的条件不再被满足时,一切又回到正轨了。

也可以在while和until循环中使用continue命令,但要特殊小心。记住,当shell执行continue命令时,它会跳过剩余的命令。如果你在这些条件中的某条中增加测试条件变量,问题就出现了:

[root@localhost ch12]# cat badtest3
#!/bin/bash
#improperly using the continue command in a while loop
var1=0
while echo "while interation: $var1"
        [ $var1 -lt 15 ]
do
        if [ $var1 -gt 5 ] && [ $var1 -lt 10 ]
        then
                continue
        fi
        echo "   Inside iteration number: $var1"
        var1=$[ $var1 +1 ]
done
[root@localhost ch12]# ./badtest3 | more
while interation: 0
   Inside iteration number: 0
while interation: 1
   Inside iteration number: 1
while interation: 2
   Inside iteration number: 2
while interation: 3
   Inside iteration number: 3
while interation: 4
   Inside iteration number: 4
while interation: 5
   Inside iteration number: 5
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
while interation: 6
--More--

你可能要确保你将脚本输出重定向到more命令,这样才能停止这些。所有一切看起来都很正常,直到满足了if-then的条件,shell执行了continue命令。当shell执行continue命令时,它会跳过while循环中的其他命令。遗憾的是是,这正是while测试命令中被测的$var1计数变量增加的地方。这意味着这个变量不会再增长了,正如你从前面连贯的输出显示中看到的。

和break命令一样,continue命令也允许通过命令行参数指定要继续哪级循环:

continue n

其中n定义了要继续的循环层数。下在是继续外部循环的一个例子:

猜你喜欢

转载自blog.csdn.net/tjjingpan/article/details/80812014