13.1 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、读取列表中的值

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

[chendajie@CHENDAJIE for.test]$ cat test1 
#!/bin/bash
# Basic for command
#
#
for test in aaa bbb ccc ddd eee
do
        echo The next state is $test
done
[chendajie@CHENDAJIE for.test]$ chmod u+x test1 
[chendajie@CHENDAJIE for.test]$ ./test1 
The next state is aaa
The next state is bbb
The next state is ccc
The next state is ddd
The next state is eee

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

[chendajie@CHENDAJIE for.test]$ cat test1b.sh   
#!/bin/bash
# Testing the for variable after the looping

for test in aaa bbb ccc ddd eee 
do
        echo "The next state is $test."
done
echo "The last state we visited was $test."
test=fff
echo "Wait, now we're visiting $test."
[chendajie@CHENDAJIE for.test]$ chmod u+x test1b.sh 
[chendajie@CHENDAJIE for.test]$ ./test1b.sh 
The next state is aaa.
The next state is bbb.
The next state is ccc.
The next state is ddd.
The next state is eee.
The last state we visited was eee.
Wait, now we're visiting fff.

2、读取列表中的复杂值

for循环有时会遇到难处理的数据。
例1:

[chendajie@CHENDAJIE for.test]$ cat badtest1 
#!/bin/bash
# Another example of how not to use the for command

for test in I don't konw if this'll wrk
do
        echo "word:$test"
done
[chendajie@CHENDAJIE for.test]$ chmod u+x badtest1 
[chendajie@CHENDAJIE for.test]$ ./badtest1 
word:I
word:dont konw if thisll
word:wrk

这段代码中的错误是:shell看到了列表值中的单引号并尝试使用它们来定义一个单独的数据值
有两种方法可以解决这个问题:
1、使用转义符( 反斜线 )来将单引号转义
2、使用双引号来定义用到单引号的值
例2:添加反斜线字符转义

[chendajie@CHENDAJIE for.test]$ cat test2.sh 
#!/bin/bash
# Another example of how not to use the for command

for test in I don\'t konw if "this'll" work
do
        echo "work:$test"
done
[chendajie@CHENDAJIE for.test]$ chmod u+x test2.sh 
[chendajie@CHENDAJIE for.test]$ ./test2.sh 
work:I
work:don't
work:konw
work:if
work:this'll
work:work

上面这段代码中,在第一个错误的地方添加了反斜线字符来转义don’t中的单引号。在第二个有问题的地方将this’ll用双引号圈起来。两种方法都能辨别出这个值。
但是,你可能遇到的另一个问题是有多个词的值。记住,for循环假定每个值都是用空格分割的,如果有包含空格的数据值。。。那就,呵呵了。
例3:

[chendajie@CHENDAJIE for.test]$ cat badtest2.sh 
#!/bin/bash
# Another example of how not to use the for command

for test in Nevada New Hampshire New Mexico New York North Carolina
do 
        echo "Now going to $test"
done
[chendajie@CHENDAJIE for.test]$ chmod u+x badtest2.sh 
[chendajie@CHENDAJIE for.test]$ ./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把所有的单词都遍历出来了,但是这并不是我想要的结果,它都没有显示完整的城市名。
如果在单独的数据值中有空格,就必须用双引号将这些值圈起来。
例4:

[chendajie@CHENDAJIE for.test]$ cat test3.sh 
#!/bin/bash
# An example of how to properly define values

for test in Nevada "New Mexico" "New York"
do
        echo "Now going to $test"
done
[chendajie@CHENDAJIE for.test]$ chmod u+x test3.sh 
[chendajie@CHENDAJIE for.test]$ ./test3.sh 
Now going to Nevada
Now going to New Mexico
Now going to New York

可以看到,此时就显示正确了。

3、从变量读取列表

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

[chendajie@CHENDAJIE for.test]$ 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
[chendajie@CHENDAJIE for.test]$ chmod u+x test3.sh 
[chendajie@CHENDAJIE for.test]$ ./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变量包含的已有列表中添加(或者说时拼接)了一个值。这是向变量中存储的已有文本字符串尾部添加文本的一个常用方法。

4、从命令中读取值

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

[chendajie@CHENDAJIE for.test]$ cat test5.sh 
#!/bin/bash
# Reading values from a file

file="states"

for state in $(cat $file)
do
        echo "Visit beautiful $state"
done
#
[chendajie@CHENDAJIE for.test]$ cat states 
Alabama
Alaska
Arizona
Arkansas
Colorado
Connecticut
Delaware
Florida
Georgia
[chendajie@CHENDAJIE for.test]$ chmod u+x test5.sh 
[chendajie@CHENDAJIE for.test]$ ./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的代码范例将文件名赋给变量,文件名中没有加入路径。这要求文件二号脚本在同一个目录中,如果不是的话,需要使用全路径名(绝对路径或相对路径)来引用文件位置。

5、更改字段分隔符

造成这个问题的原因是特殊的环境变量IFS,叫做内部字段分隔符(internal field separator)。IFS环境变量定义了bash shell用作字段分隔符的一些列字符。默认情况下,bash shell会将下列字符当作字段分隔符:
1、空格
2、制表符
3、换行符
如果bash shell在数据中看到了这些字符中的任意一个,它就会假定这表明了列表中一个新数据字段的开始。在处理可能含有空格的数据(比如文件名)时,这会非常麻烦。
要解决这个问题,可以在shell脚本中临时更改IFS环境变量的值来限制被bash shell当作字段分隔符的字符。例如,如果你想修改IFS的值,使其只能识别换行符,那么必须这么做:
IFS=$’\n’
将这个语句加入到脚本中,告诉bash shell在数据值中忽略空格和制表符。例:

[chendajie@CHENDAJIE for.test]$ cat states2
Alabama
Alaska
Arizona
Arkansas
Colorado
Connecticut
Delaware
Florida
Georgia
New York
New Hampshire
North Carolina
[chendajie@CHENDAJIE for.test]$ cat test5b.sh 
#!/bin/bash
# Reading values from a file

file="states2"

IFS=$'\n'
for state in $(cat $file)
do
        echo "Visit beautiful $state"
done
#
[chendajie@CHENDAJIE for.test]$ chmod u+x test5b.sh 
[chendajie@CHENDAJIE for.test]$ ./test5b.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的默认值。
假如你要遍历一个文件中用冒号分隔的值(比如在/etc/passwd文件中)。你要做的就是将IFS的值设为冒号。
IFS=:
如果要指定多个IFS字符,只要将它们在赋值行串起来就行。
IFS=$’\n’:;"
这个赋值会将换行符、冒号、分号和双引号作为字段分隔符。

6、用通配符读取目录

最后,可以用for命令来自动遍历目录中的文件。进行此操作是,必须在文件名或路径名中使用通配符,它会强制shell使用文件扩展匹配。文件扩展匹配是生成匹配指定通配符的文件名或路径名的过程。
如果不知道所有的文件名,这个特性在处理目录中的文件是就非常好用。

[chendajie@CHENDAJIE for.test]$ ls /home/chendajie/test/my_test
1.txt  atxt  btxt  c.test  ctxt  shells  sh.test  test
[chendajie@CHENDAJIE for.test]$ cat test6.sh 
#!/bin/bash
# Iterate through all the files in a directory

for file in /home/chendajie/test/my_test/*
do
        if [ -d "$file" ]
        then
                echo "$file is a directory"
        elif [ -f "$file" ]
        then
                echo "$file is a file"
        fi
done
[chendajie@CHENDAJIE for.test]$ chmod u+x test6.sh 
[chendajie@CHENDAJIE for.test]$ ./test6.sh 
/home/chendajie/test/my_test/1.txt is a file
/home/chendajie/test/my_test/atxt is a file
/home/chendajie/test/my_test/btxt is a file
/home/chendajie/test/my_test/c.test is a directory
/home/chendajie/test/my_test/ctxt is a file
/home/chendajie/test/my_test/shells is a directory
/home/chendajie/test/my_test/sh.test is a directory
/home/chendajie/test/my_test/test is a directory

for命令会遍历/home/chendajie/test/my_test/*的输出结果。该代码用test命令测试了每个条目(使用方括号的方法),以查看它是目录(通过-d参数)还是文件(通过-f参数)
【注意】我们在这个例子的if语句中做了一些不同和的处理:
if [ -d “$file” ]
在Linux中,目录名和文件名包含空格是合法的,要适应这种情况,应该将file变量用双引号圈起来。如果不这样做,遇到含有空格的目录名或文件名就会有错误。
在for命令中,也可以列出多个目录通配符,将目录查找和列表合并进同一个for语句。

[chendajie@CHENDAJIE for.test]$ cat test7.sh 
#!/bin/bash
# Iterating through multiple directories

for file in /home/chendajie/test/my_test/* /home/chendajie/test/my_test2/*
do
        if [ -d "$file" ]
        then
                echo "$file is a directory"
        elif [ -f "$file" ]
        then
                echo "$file is a file"
        else
                echo "$file doesn't exist"
        fi
done
#
[chendajie@CHENDAJIE for.test]$ chmod u+x test7.sh 
[chendajie@CHENDAJIE for.test]$ ./test7.sh 
/home/chendajie/test/my_test/1.txt is a file
/home/chendajie/test/my_test/atxt is a file
/home/chendajie/test/my_test/btxt is a file
/home/chendajie/test/my_test/c.test is a directory
/home/chendajie/test/my_test/ctxt is a file
/home/chendajie/test/my_test/shells is a directory
/home/chendajie/test/my_test/sh.test is a directory
/home/chendajie/test/my_test/test is a directory
/home/chendajie/test/my_test2/for.test is a directory

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值