本章内容:
- for 语句
- until 语句
- while 语句
- 多重循环
- 重定向循环的输出
需要重复多个命令直至达到某个特定条件,比如处理目录下的所有文件、系统中的所有用户或是文本文件中的所有行。bash shell 提供了 for 命令,以允许创建遍历一系列值的循环。
for var in list
do
commands
done
for 命令最基本的用法是遍历其自身所定义的一系列值。
# 1.脚本内容。(遍历读取值。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
for name in jack lucy rose tom
do
echo "the next name is $name."
done
# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
the next name is jack.
the next name is lucy.
the next name is rose.
the next name is tom.
有的时候,会遇到难处理的数据。
# 1.脚本内容。(内容中有单引号的数据。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
for test in I don't know if this'll work
do
echo "word:$test"
done
# 2.执行脚本。(单引号丢失,输出与期望不符合。)
[root@VM-8-11-centos testdir]# ./test.sh
word:I
word:dont know if thisll
word:work
# 1.脚本内容。(分别使用转义字符和双引号来解决该问题。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
for test in I don\'t know if "this'll" work
do
echo "word:$test"
done
# 2.执行脚本。(输出内容符合预期。)
[root@VM-8-11-centos testdir]# ./test.sh
word:I
word:don't
word:know
word:if
word:this'll
word:work
# 1.脚本内容。(内容中有多个单词。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
for place in New York New Hampshire New Mexico
do
echo "now going to $place"
done
# 2.执行脚本。(空格被分隔,输出与期望不符合。)
[root@VM-8-11-centos testdir]# ./test.sh
now going to New
now going to York
now going to New
now going to Hampshire
now going to New
now going to Mexico
# 1.脚本内容。(如果某个值含有空格,则必须将其放入双引号内。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
for place in "New York" "New Hampshire" "New Mexico"
do
echo "now going to $place"
done
# 2.执行脚本。(输出内容符合预期。)
[root@VM-8-11-centos testdir]# ./test.sh
now going to New York
now going to New Hampshire
now going to New Mexico
在 shell 脚本中经常遇到的情况是,你将一系列值集中保存在了一个变量中,然后需要遍历该变量中的整个值列表。
# 1.脚本内容。($list变量包含了用于迭代的值列表。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
list="A B C D"
list=$list" E"
#
for value in $list
do
echo "value=$value"
done
# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
value=A
value=B
value=C
value=D
value=E
生成值列表的另一种途径是使用命令的输出。你可以用命令替换来执行任何能产生输出的命令,然后在 for 命令中使用该命令的输出。
# 1.脚本内容。(对cat命令输出的文件内容进行遍历。)
# 提示:由于data.txt文件和脚本在同一目录则可以直接通过文件名读取,否则需要加上文件路径。
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
file=data.txt
for value in $(cat $file)
do
echo "value=$value"
done
# 2.数据文件内容。(注意文件中每个值各占一行,而不是以空格分隔。)
[root@VM-8-11-centos testdir]# cat data.txt
A
B
C
D
E
# 3.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
value=A
value=B
value=C
value=D
value=E
for 命令仍然以每次一行的方式遍历 cat
命令的输出。但这并没有解决数据中含有空格的问题。如果你列出了一个含有空格的值,则 for 命令仍然会用空格来分隔值。
空格分隔问题:
# 1.数据文件内容。(D所在行添加空格及值X。)
[root@VM-8-11-centos testdir]# cat data.txt
A
B
C
D X
E
# 2.执行脚本。(D和X没有视为同一行,输出结果不符合预期。)
[root@VM-8-11-centos testdir]# ./test.sh
value=A
value=B
value=C
value=D
value=X
value=E
造成这个问题的原因是特殊的环境变量 IFS( internal field separator,内部字段分隔符)。IFS 环境变量定义了 bash shell 用作字段分隔符的一系列字符。在默认情况下,bash shell 会将下列字符视为字段分隔符。
- 空格
- 制表符
- 换行符
IFS=$'\n'
# 1.脚本内容。(IFS=$'\n'表示只识别换行符作为分隔符。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
file=data.txt
#
IFS=$'\n'
#
for value in $(cat $file)
do
echo "value=$value"
done
# 2.数据文件内容。(D所在行添加了空格及值X。)
[root@VM-8-11-centos testdir]# cat data.txt
A
B
C
D X
E
# 3.执行脚本。(只按换行符分隔,输出结果符合预期。)
[root@VM-8-11-centos testdir]# ./test.sh
value=A
value=B
value=C
value=D X
value=E
old=$IFS
IFS=$'\n'
# <在代码中使用新的 IFS 值>
IFS=$old
IFS=:
# 该语句会将换行符、冒号、分号和双引号作为字段分隔符。
IFS=$'\n:;"'
最后,还可以用 for 命令来自动遍历目录中的文件。为此,必须在文件名或路径名中使用通配符,这会强制 shell 使用文件名通配符匹配( file globbing )。
文件名通配符匹配是生成与指定通配符匹配的文件名或路径名的过程。
应用示例:
# 1.脚本内容。
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
#
path1=/home/tmp/*
path2=/home/tmp/AAA.log
#
for file in $path1 $path2
do
#
if [ -d $file ]
then
echo "$file is directory."
#
elif [ -f $file ]
then
echo "$file is file."
#
else
echo "$file doesn't exist"
fi
#
done
# 2.脚本中指定的路径存在哪些文件和目录。
[root@VM-8-11-centos testdir]# ls -F /home/tmp/
a.txt b.txt c.txt dir1/ dir2/
# 3.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
/home/tmp/a.txt is file.
/home/tmp/b.txt is file.
/home/tmp/c.txt is file.
/home/tmp/dir1 is directory.
/home/tmp/dir2 is directory.
/home/tmp/AAA.log doesn't exist
bash shell 脚本中可以使用仿 C 语言的 for 命令。
C 语言中的 for 命令包含循环变量初始化、循环条件以及每次迭代时修改变量的方法。
for (i = 0; i < 10; i++)
{
printf("The next number is %d\n", i);
}
for (( variable assignment ; condition ; iteration process ))
# 举个例子:
for (( a = 1; a < 10; a++ ))
# 1.脚本内容。
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
for (( i=1; i<=10; i++ ))
do
echo "num=$i"
done
# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
num=1
num=2
num=3
num=4
num=5
num=6
num=7
num=8
num=9
num=10
expr
命令格式。仿 C 语言的 for 命令也允许为迭代使用多个变量。循环会单独处理每个变量,你可以为每个变量定义不同的迭代过程。尽管可以使用多个变量,但只能在 for 循环中定义一种迭代条件。
# 1.脚本内容。(同时迭代两个变量:a自增,b自减。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
for (( a=1,b=10; a<=10; a++,b-- ))
do
echo "a=$a - b=$b"
done
# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
a=1 - b=10
a=2 - b=9
a=3 - b=8
a=4 - b=7
a=5 - b=6
a=6 - b=5
a=7 - b=4
a=8 - b=3
a=9 - b=2
a=10 - b=1
while 命令允许定义一个要测试的命令,只要该命令返回的退出状态码为 0,就循环执行一组命令。
while test command
do
commands
done
while 命令中定义的 test command 与 if-then 语句中的格式一模一样。
while 命令的关键在于所指定的 test command 的退出状态码必须随着循环中执行的命令而改变。如果退出状态码不发生变化,那 while 循环就成了死循环。
test command 最常见的用法是使用方括号来检查循环命令中用到的 shell 变量值。
应用示例:
# 1.脚本内容。(判断条件:变量大于0就循环;迭代条件:变量每次自减1)。
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
num=3
while [ $num -gt 0 ]
do
echo "num=$num"
#
num=$[ $num -1 ]
#
done
# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
num=3
num=2
num=1
while 命令允许在 while 语句行定义多个测试命令。只有最后一个测试命令的退出状态码会被用于决定是否结束循环。如果你不小心,这可能会导致一些有意思的结果。
# 1.脚本内容。(while 语句中定义了两个测试命令,一是显示当前变量值;二是判断变量的值。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
num=3
while echo "num=$num"
[ $num -gt 0 ]
do
echo " -> inside the loop."
#
num=$[ $num -1 ]
#
done
# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
num=3
-> inside the loop.
num=2
-> inside the loop.
num=1
-> inside the loop.
num=0
与 while 命令工作的方式完全相反,until 命令要求指定一个返回非 0 退出状态码的测试命令。一旦测试命令返回了退出状态码 0,循环就结束了。
until test command
do
commands
done
# 1.脚本内容。(直到变量值为0时循环停止。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
num=100
until [ $num -eq 0 ]
do
#
echo "num=$num"
#
num=$[ $num -25 ]
done
# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
num=100
num=75
num=50
num=25
# 1.脚本内容。(变量每次自减25,直到等于0。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
num=100
until echo "num=$num"
[ $num -eq 0 ]
do
#
echo " -> inside the loop."
#
num=$[ $num -25 ]
done
# 2.执行脚本。(仅当最后一个命令成立时才停止。)
[root@VM-8-11-centos testdir]# ./test.sh
num=100
-> inside the loop.
num=75
-> inside the loop.
num=50
-> inside the loop.
num=25
-> inside the loop.
num=0
循环语句可以在循环内使用任意类型的命令,包括其他循环命令,这称为嵌套循环。注意,在使用嵌套循环时是在迭代中再进行迭代,命令运行的次数是乘积关系。
# 1.脚本内容。(外层循环两次,各内层循环两次。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
for (( a=1; a<3; a++ ))
do
echo "outer -> $a"
#
for (( b=1; b<3; b++ ))
do
echo -e "\t inside -> $b"
done
#
done
# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
outer -> 1
inside -> 1
inside -> 2
outer -> 2
inside -> 1
inside -> 2
这个被嵌套的循环(也称为内层循环)会在外部循环的每次迭代中遍历一遍它所有的值。
还可以混用循环,比如 while 循环内部放置 for 循环:
# 1.脚本内容。(控制外层循环的变量每次自减5;控制内层循环的变量自减1。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
a=10
while [ $a -gt 0 ]
do
echo "outer -> $a"
#
for (( b=1; b<3; b++ ))
do
echo -e "\t inside -> $b"
done
#
a=$[ $a -5 ]
done
# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
outer -> 10
inside -> 1
inside -> 2
outer -> 5
inside -> 1
inside -> 2
你经常需要遍历文件中保存的数据。这要求综合运用以下两种技术:
- 使用嵌套循环。
- 修改 IFS 环境变量。
# 1.脚本内容。(这里使用了两个不同的 IFS 值来解析数据。第一个 IFS 值解析出/etc/passwd 文件中的各行。内层 for 循环接着将 IFS 的值修改为冒号,以便解析出/etc/passwd 文件各行中的字段。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
path=/etc/passwd
old=$IFS
IFS=$'\n'
#
for entry in $(cat $path)
do
echo "entry -> $entry"
#
IFS=:
#
for value in $entry
do
echo -e "\t $value"
done
#
done
#
IFS=$old
# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
entry -> root:x:0:0:root:/root:/bin/bash
root
x
0
0
root
/root
/bin/bash
...
有两个命令可以控制循环的结束时机:
- break 命令。
- continue 命令。
你可以用 break 命令退出任意类型的循环,包括 while 循环和 until 循环。
# 1.脚本内容。(变量自增到3的时候退出循环。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
for (( i=0; i<10; i++ ))
do
echo "i=$i"
#
if [ $i -eq 3 ]
then
break;
fi
#
done
# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
i=0
i=1
i=2
i=3
# 1.脚本内容。(控制内层循环次数。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
for (( a=1; a<3; a++ ))
do
echo "outer->$a"
#
for (( b=1; b<100; b++ ))
do
echo -e "\t inside->$b"
#
if [ $b -eq 2 ]
then
break
fi
#
done
#
done
# 2.执行脚本。(内层循环最多只执行两次。)
[root@VM-8-11-centos testdir]# ./test.sh
outer->1
inside->1
inside->2
outer->2
inside->1
inside->2
break n
# 1.脚本内容。(将break设置为2,进入逻辑则终止外层循环。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
for (( a=1; a<3; a++ ))
do
echo "outer->$a"
#
for (( b=1; b<100; b++ ))
do
echo -e "\t inside->$b"
#
if [ $b -eq 2 ]
then
break 2
fi
#
done
#
done
# 2.执行脚本。(外层循环只执行了一次。)
[root@VM-8-11-centos testdir]# ./test.sh
outer->1
inside->1
inside->2
continue 命令可以提前中止某次循环,但不会结束整个循环。
# 1.脚本内容。(跳过10以内的偶数。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
for (( i=0; i<10; i++ ))
do
evenNum=$[$i % 2]
#
if [ $evenNum -eq 0 ]
then
continue
fi
#
echo "$i"
done
# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
1
3
5
7
9
continue n
在 shell 脚本中,可以对循环的输出进行重定向或进行管道操作。
# 1.脚本内容。(遍历目标目录的文件,将输出信息重定向至新文本。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
files=/home/tmp/*
#
for file in $files
do
#
if [ -f $file ]
then
echo "$file is file. "
fi
#
done > output.txt
#
# 2.列出指定目录的文件有哪些。
[root@VM-8-11-centos testdir]# ls -F /home/tmp/
a.txt b.txt c.txt dir1/ dir2/
# 3.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
# 4.查看生成的重定向文本。
[root@VM-8-11-centos testdir]# cat output.txt
/home/tmp/a.txt is file.
/home/tmp/b.txt is file.
/home/tmp/c.txt is file.
# 1.脚本内容。(将元素通过管道符传给sort命令进行排序输出。)
[root@VM-8-11-centos testdir]# cat test.sh
#!/bin/bash
list="D B A C"
for val in $list
do
echo "$val"
done | sort
# 2.执行脚本。
[root@VM-8-11-centos testdir]# ./test.sh
A
B
C
D
“-------怕什么真理无穷,进一寸有一寸的欢喜。”
微信公众号搜索:饺子泡牛奶。