13.1 for命令

for var in list do commands done

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

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

也可以将do语句和for语句放在同一行,但必须用分号将其同列表中的值分

开:for var in list; do。

13.1.1 读取列表中的值

结构化讲解(更多的结构化命令)(1)

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

结构化讲解(更多的结构化命令)(2)

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

13.1.2 读取列表中的复杂值

单引号带来的问题:

结构化讲解(更多的结构化命令)(3)

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

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

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

结构化讲解(更多的结构化命令)(4)

for循环假定每个值都是用空格分割的.如果在单独的数据值中有空格,就必须用双引号将这些值圈起来。

结构化讲解(更多的结构化命令)(5)

13.1.3 从变量读取列表

一系列值都集中存储在了一个变量中,然后需要遍历变量中的整个列表。也可以通过for命令完成这个任务。

结构化讲解(更多的结构化命令)(6)

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

13.1.4 从命令读取值

生成列表中所需值的另外一个途径就是使用命令的输出。可以用命令替换来执行任何能产生输出的命令,然后在for命令中使用该命令的输出。

结构化讲解(更多的结构化命令)(7)

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

13.1.5 更改字段分隔符

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

 空格

 制表符

 换行符

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

要解决这个问题,可以在shell脚本中临时更改IFS环境变量的值来限制被bash shell当作字段分隔符的字符。例如,如果你想修改IFS的值,使其只能识别换行符,那就必须这么做:

IFS=$'\n'

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

结构化讲解(更多的结构化命令)(8)

在处理代码量较大的脚本时,可能在一个地方需要修改IFS的值,然后忽略这次修改,在

脚本的其他地方继续沿用IFS的默认值。一个可参考的安全实践是在改变IFS之前保存原

来的IFS值,之后再恢复它。

这种技术可以这样实现:

IFS.OLD=$IFS

IFS=$'\n'

<在代码中使用新的IFS值>

IFS=$IFS.OLD

这就保证了在脚本的后续操作中使用的是IFS的默认值。

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

IFS=:

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

IFS=$'\n':;"

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

13.1.6 用通配符读取目录

结构化讲解(更多的结构化命令)(9)

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

if [ -d "$file" ]

在Linux中,目录名和文件名中包含空格当然是合法的。要适应这种情况,应该将$file变量用双引号圈起来

也可以在for命令中列出多个目录通配符,将目录查找和列表合并进同一个for语句

结构化讲解(更多的结构化命令)(10)

13.3 while命令

while命令允许定义一个要测试的命令,然后循环执行一组命令,只要定义的测试命令返回的是退出状态码0。它会在每次迭代的一开始测试test命令。在test命令返回非零退出状态码时,while命令会停止执行那组命令。

13.3.1 while的基本格式

while test command do other commands done

while命令的关键在于所指定的test command的退出状态码必须随着循环中运行的命令而改变。如果退出状态码不发生变化, while循环就将一直不停地进行下去。

最常见的test command的用法是用方括号来检查循环命令中用到的shell变量的值。

结构化讲解(更多的结构化命令)(11)

13.3.2 使用多个测试命令

while命令允许你在while语句行定义多个测试命令。只有最后一个测试命令的退出状态码会被用来决定什么时候结束循环.

结构化讲解(更多的结构化命令)(12)

while语句中定义了两个测试命令。

while echo $var1

[ $var1 -ge 0 ]

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

This is inside the loop

-1

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

在含有多个命令的while语句中,在每次迭代中所有的测试命令都会被执行,包括测试命令失败的最后一次迭代。

注意,每个测试命令都出现在单独的一行上。

13.4 until命令

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

until test commands do other commands done

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

结构化讲解(更多的结构化命令)(13)

同while命令一样,在until命令中使用多个测试命令时要注意。

结构化讲解(更多的结构化命令)(14)

13.5 嵌套循环

循环语句可以在循环内使用任意类型的命令,包括其他循环命令。这种循环叫作嵌套循环(nested loop)。

结构化讲解(更多的结构化命令)(15)

13.6 循环处理文件数据

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

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

环境变量IFS,叫作内部字段分隔符(internal field separator)。IFS环境变量定义了bash shell用作字段分隔符的一系列字符。默认情况下,bash shell会将下列字符当作字段分隔符:

如果你想修改IFS的值,使其只能识别换行符:IFS=$'\n'

将这个语句加入到脚本中,告诉bash shell在数据值中忽略空格和制表符。

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

结构化讲解(更多的结构化命令)(16)

在运行这个脚本时,会得到如下输出。

结构化讲解(更多的结构化命令)(17)

13.7 控制循环

有两个命令能帮我们控制循环内部的情况:

13.7.1 break命令

可以用break命令来退出任意类型的循环,包括while和until循环。

1. 跳出单个循环

在shell执行break命令时,它会尝试跳出当前正在执行的循环。

结构化讲解(更多的结构化命令)(18)

结构化讲解(更多的结构化命令)(19)

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

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

2. 跳出内部循环

在处理多个循环时,break命令会自动终止你所在的最内层的循环。

结构化讲解(更多的结构化命令)(20)

3. 跳出外部循环

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

break n

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

结构化讲解(更多的结构化命令)(21)

13.7.2 continue命令

continue命令可以提前中止某次循环中的命令,但并不会完全终止整个循环。可以在循环内部设置shell不执行命令的条件。

结构化讲解(更多的结构化命令)(22)

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

continue n

其中n定义了要继续的循环层级。

结构化讲解(更多的结构化命令)(23)

13.8 处理循环的输出

在shell脚本中,可以对循环的输出使用管道或进行重定向。这可以通过在done命令之后添加一个处理命令来实现。

下面将for命令的输出重定向到文件,而不是显示在屏幕上。

结构化讲解(更多的结构化命令)(24)

这种方法同样适用于将循环的结果管接给另一个命令。

结构化讲解(更多的结构化命令)(25)

state值并没有在for命令列表中以特定次序列出。for命令的输出传给了sort命令,该命令会改变for命令输出结果的顺序。运行这个脚本实际上说明了结果已经在脚本内部排好序了。

,