shell脚本之for循环

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 。

 

1.1.读取列表中的值

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

[ceshi@jerry jghml]$ cat test1.sh

#!/bin/bash

# basic for command


for test in Alabama Alaska Arizona Arknsas California Colorado

do

    echo The next state is $test

done

[ceshi@jerry jghml]$ sh test1.sh

The next state is Alabama

The next state is Alaska

The next state is Arizona

The next state is Arknsas

The next state is California

The next state is Colorado

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

[ceshi@jerry jghml]$ cat test1a.sh

#!/bin/bahs

# testing the for variable after the looping


for test in Alabama Alaska Arizona Arkansas Cakufornia 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"

[ceshi@jerry jghml]$ sh test1a.sh

The next state is Alabama

The next state is Alaska

The next state is Arizona

The next state is Arkansas

The next state is Cakufornia

The next state is Colorado

The last state we visited was Colorado

Wait, now we're visiting Connecticut

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

 

1.2 读取列表中的复杂值

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

[ceshi@jerry jghml]$ cat badtest1.sh

#!/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 "word:$test"

done

[ceshi@jerry jghml]$ sh badtest1.sh

word:I

word:dont know if thisll

word:work

shell看到了列表值中的单引号并尝试使用它们来定义一个单独的数据值,这真是把事情搞得一团糟。

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

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

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

[ceshi@jerry jghml]$ cat badtest1.sh

#!/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 "word:$test"

done

[ceshi@jerry jghml]$ sh badtest1.sh

word:I

word:don't

word:know

word:if

word:this'll

word:work

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

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

[ceshi@jerry jghml]$ cat badtest2.sh

#!/bin/bash

# another example of how not to use the for commnd



for test in Nevada New Hampshire New Mexico New York North Carolina

do

   echo "Now going to $test"

done

[ceshi@jerry jghml]$ sh badtest2.sh

Now going to Nevada

Now going to New

Now going to Hampshire

Now going to New

Now going to Mexico

Now going to New

Now going to York

Now going to North

Now going to Carolina

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

[ceshi@jerry jghml]$ cat badtest2.sh

#!/bin/bash

# another example of how not to use the for commnd



for test in Nevada "New Hampshire" "New Mexico" "New York" "North Carolina"

do

   echo "Now going to $test"

done

[ceshi@jerry jghml]$ sh badtest2.sh

Now going to Nevada

Now going to New Hampshire

Now going to New Mexico

Now going to New York

Now going to North Carolina

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

 

1.3 从变量读取列表

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

[ceshi@jerry jghml]$ cat test4.sh

#!/bin/bash

#using a variable to hold the list



list="Alabama Alaska Arizona Arkansas colorado"

list=$list" Connecticut"



for state in $list

do

   echo "Have you ever visited $state?"

done

[ceshi@jerry jghml]$ sh test4.sh

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 Connecticut?

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

 

1.4 从命令读取值

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

 

[ceshi@jerry jghml]$ cat test5.sh

#!/bin/bash

# reading values from a file



file="states"



for state in $(cat $file)

do

    echo "Visit beautiful $state"

done

[ceshi@jerry jghml]$ cat states

Alabama

Alaska

Arizona

Arkansas

Colorado

Connecticut

Delaware

Florida

Georgia

[ceshi@jerry jghml]$ sh test5.sh

Visit beautiful Alabama

Visit beautiful Alaska

Visit beautiful Arizona

Visit beautiful Arkansas

Visit beautiful Colorado

Visit beautiful Connecticut

Visit beautiful Delaware

Visit beautiful Florida

Visit beautiful Georgia

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

 

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

 

1.5 更改字段分隔符

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

  空格

  制表符

  换行符

如果bash shell在数据中看到了这些字符中的任意一个,它就会假定这表明了列表中一个新数据字段的开始。在处理可能含有空格的数据(比如文件名)时,这会非常麻烦,就像你在上一个脚本示例中看到的。要解决这个问题,可以在shell脚本中临时更改 IFS 环境变量的值来限制被bash shell当作字段分隔符的字符。例如,如果你想修改 IFS 的值,使其只能识别换行符,那就必须这么做:

IFS=$'\n'

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

[ceshi@jerry jghml]$ cat test5.sh

#!/bin/bash

# reading values from a file



file="states"



IFS=$'\n'

for state in $(cat $file)

do

    echo "Visit beautiful $state"

done

[ceshi@jerry jghml]$ sh test5.sh

Visit beautiful Alabama

Visit beautiful Alaska

Visit beautiful Arizona

Visit beautiful Arkansas

Visit beautiful Colorado

Visit beautiful Connecticut

Visit beautiful Delaware

Visit beautiful Florida

Visit beautiful Georgia

Visit beautiful New york

Visit beautiful New Hampshire

Visit beautiful North Carolina

 

注意:在处理代码量较大的脚本时,可能在一个地方需要修改 IFS 的值,然后忽略这次修改,在脚本的其他地方继续沿用 IFS 的默认值。一个可参考的安全实践是在改变 IFS 之前保存原

来的 IFS 值,之后再恢复它。这种技术可以这样实现:

IFS.OLD=$IFS

IFS=$'\n'

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

IFS=$IFS.OLD

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

 

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

IFS=:

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

IFS=$'\n':;"

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

 

1.6 用通配符读取目录

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

如果不知道所有的文件名,这个特性在处理目录中的文件时就非常好用。

[ceshi@jerry jghml]$ cat test6.sh

#!/bin/bash

# iterate through all the files in a directory



for file in /home/ceshi/jghml/*

do

    if [ -d "$file" ]

    then

       echo "$file is a directory"

    elif [ -f "$file" ]

    then

       echo "$file is a file"

    fi

done

[ceshi@jerry jghml]$ sh test6.sh

/home/ceshi/jghml/badtest1.sh is a file

/home/ceshi/jghml/badtest2.sh is a file

/home/ceshi/jghml/states is a file

/home/ceshi/jghml/states1 is a directory

/home/ceshi/jghml/test1a.sh is a file

/home/ceshi/jghml/test1.sh is a file

/home/ceshi/jghml/test4.sh is a file

/home/ceshi/jghml/test5.sh is a file

/home/ceshi/jghml/test6.sh is a file

for 命令会遍历/home/ceshi/jghml/*输出的结果。该代码用 test 命令测试了每个条目(使用方括号方法),以查看它是目录(通过 -d 参数)还是文件(通过 -f 参数)。注意,我们在这个例子的 if 语句中做了一些不同的处理:

if [ -d "$file" ]

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

sh test6: line 6: [: too many arguments

sh test6: line 9: [: too many arguments

在 test 命令中,bash shell会将额外的单词当作参数,进而造成错误。也可以在 for 命令中列出多个目录通配符,将目录查找和列表合并进同一个 for 语句。

 

[ceshi@jerry jghml]$ cat test7.sh

#!/bin/bash

# iterating through multiple direcories



for file in /home/ceshi/t* /home/ceshi/jghml /home/badtest

do

    if [ -d "$file" ]

    then

       echo "$file is a directory"

    elif [ -f "$file" ]

    then

       echo "$file is a file"

    else

       echo "$file doesn't exust"

    fi

done

[ceshi@jerry jghml]$ sh test7.sh

/home/ceshi/test14.sh is a file

/home/ceshi/test16.sh is a file

/home/ceshi/test17.sh is a file

/home/ceshi/test18.sh is a file

/home/ceshi/test23.sh is a file

/home/ceshi/test24.sh is a file

/home/ceshi/test26.sh is a file

/home/ceshi/tset25.sh is a file

/home/ceshi/jghml is a directory

/home/badtest doesn't exust

 

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

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

 

2.C 语言风格的 for 命令

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

 

2.1 C 语言的 for 命令

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

for (i = 0; i < 10; i++)

{

printf("The next number is %d\n", i);

}

这段代码产生了一个简单的迭代循环,其中变量 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 命令。

[ceshi@jerry jghml]$ cat test8.sh

#!/bin/bash

# testing the C-style for loop



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

do

    echo "The next number is $i"

done

[ceshi@jerry jghml]$ sh test8.sh

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 循环中赋予的值。在每次迭代后,循环的迭代过程会作用在变量上,在本例中,变量增一。

 

2.2 使用多个变量

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

[ceshi@jerry jghml]$ cat test9.sh

#!/bin/bash

# multiple variables



for (( a=1, b=10; a <= 10; a++, b-- ))

do

    echo "$a - $b"

done

[ceshi@jerry jghml]$ sh test9.sh

1 - 10

2 - 9

3 - 8

4 - 7

5 - 6

6 - 5

7 - 4

8 - 3

9 - 2

10 - 1

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

  • 3
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值