1 for命令
bash shell提供了for命令,允许我们创建一个遍历一系列值的循环。每次迭代都使用其中一个
值来执行已定义好的一组命令。下面是bash shell中for命令的基本格式:
for var in list
do
commands
done
在list参数中,需要提供迭代中要用到的一系列值。可以通过几种不同的方法指定列表中的值。
在每次迭代中,变量var会包含列表中的当前值。第一次迭代会使用列表中的第一个值,第
二次迭代使用第二个值,以此类推,直到列表中的所有值都过一遍。
在do和done语句之间输入的命令可以是一条或多条标准的bash shell命令。在这些命令中,$var变量包含着这次迭代对应的当前列表项中的值。
也可以这么表示:
for var in list; do
1.1 读取列表中的值
for test in Alabama Alaska Arizona Arkansas California Colorado; do
echo The next state is $test
done
$ bash test1.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 California
The next state is Colorado
每次for命令遍历值列表,它都会将列表中的下个值赋给$test变量。
test变量可以像for命令语句中的其他脚本变量一样使用。在最后一次迭代后,$test变量的值会在shell脚本的剩余部分一直保持有效。
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 are visiting $test"
$ bash test1.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 California
The next state is Colorado
The last state we visited was Colorado
Wait, now we are visiting Connecticut
$test变量保持了其值,也允许我们修改它的值,并在for命令循环之外跟其他变量一样使用。
1.2 读取列表中的复杂值
有两种办法可解决这个问题:
使用转义字符(反斜线)来将单引号转义;
使用双引号来定义用到单引号的值。
for test in I don\'t know if "this'll" work; do
echo "word:$test"
done
$ bash test2.sh
word:I
word:don't
word:know
word:if
word:this'll
word:work
在第一个有问题的地方添加了反斜线字符来转义don't中的单引号。在第二个有问题的地方
将this'll用双引号圈起来。两种方法都能正常辨别出这个值。
可能遇到的另一个问题是有多个词的值。记住,for循环假定每个值都是用空格分割的。
for命令用空格来划分列表中的每个值。如果在单独的数据值中有空格,就必须用双引号将这些值圈起来:
for test in Nevada "New Hampshire" "New Mexico" "New York"; do
echo "Now going to $test"
done
Now going to Nevada
Now going to New Hampshire
Now going to New Mexico
Now going to New York
1.3 从变量读取列表
通常shell脚本遇到的情况是,你将一系列值都集中存储在了一个变量中,然后需要遍历变量
中的整个列表。也可以通过for命令完成这个任务。
list="Alabama Alaska Arizona Arkansas Colorado"
list=$list" Connecticut"
for state in $list; do
echo "Have you ever visited $state?"
done
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命令中使用该命令的输出。
file="states"
for state in $(cat $file); do
echo "Visit beautiful $state"
done
$ cat states
Alabama
Alaska
Arizona
Arkansas
Colorado
Connecticut
Delaware
Florida
Georgia
$ bash test1.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命令的输出,假定每个州都是在单独的一行上。
1.5 更改字段分隔符
默认情况下,bash shell会将下列字符当作字段分隔符:
空格
制表符
换行符
例如,如果我们想修改IFS的值,使其只能识别换行符,那就必须这么做:
IFS=$'\n'
将这个语句加入到脚本中,告诉bash shell在数据值中忽略空格和制表符。对前一个脚本使用
这种方法,将获得如下输出。
file="states"
IFS=$'\n'
for state in $(cat $file); do
echo "Visit beautiful $state"
done
$ bash test1.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 Carolina
现在,shell脚本旧能够使用列表中含有空格的值了。
注意
在处理代码量较大的脚本时,可能在一个地方需要修改IFS的值,然后忽略这次修改,在脚本的其他地方继续沿用IFS的默认值。一个可参考的安全实践是在改变IFS之前保存原来的IFS值,之后再恢复它。
这种技术可以这样实现:
IFS.OLD=$IFS
IFS=$'\n'
<在代码中使用新的IFS值>
IFS=$IFS.OLD
这就保证了在脚本的后续操作中使用的是IFS的默认值。
假定我们要遍历一个文件中用冒号分隔的值(比如在/etc/passwd文件中)。我们要做的就是将IFS的值设为冒号。
IFS=:
如果要指定多个IFS字符,只要将它们在赋值行串起来就行。
IFS=$'\n':;"
这个赋值会将换行符、冒号、分号和双引号作为字段分隔符。
1.6 用通配符读取目录
最后,可以用for命令来自动遍历目录中的文件。
for file in /home/kaoku/data/*; do
if [ -d "$file" ]
then
echo "$file is a directory"
elif [ -f "$file" ]
then
echo "$file is a file"
fi
done
for命令会遍历/home/kaoku/data/*输出的结果。
该代码用test命令测试了每个条目(使用方括号方法),以查看它是目录(通过-d参数)还是文件(通过-f参数)。
也可以在for命令中列出多个目录通配符,将目录查找和列表合并进同一个for语句。
for file in /home/kaoku/data/.b* /home/kaoku/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 exist"
fi
done
for语句首先使用了文件扩展匹配来遍历通配符生成的文件列表,然后它会遍历列表中的下
一个文件。可以将任意多的通配符放进列表中。
补充:循环 for
for i in a.txt b.txt; do
for i in 'ls data/*.txt'; do 匹配某类文件作为输入
for i in 'cat list.txt'; do 使用文本为输入列表
for i in 'cat list.txt|cut -f 1'; do 指定某列作为输入文件名
plot_heatmap.sh -i data/${i} -o heatmap/${i}.pdf
done
1.7 C 语言风格的for 命令
for (i = 0; i < 10; i++)
{
printf("The next number is %d\n", i);
}
for (( i=1; i <= 10; i++ ))
do
echo "The next number is $i"
done
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 (( a=1, b=10; a <= 10; a++, b-- ))
do
echo "$a - $b"
done
1 - 10
2 - 9
3 - 8
4 - 7
5 - 6
6 - 5
7 - 4
8 - 3
9 - 2
10 - 1
2 while命令
while命令某种意义上是if-then语句和for循环的混杂体。
while命令允许定义一个要测试的命令,然后循环执行一组命令,只要定义的测试命令返回的是退出状态码0。它会在每次迭代的一开始测试test命令。在test命令返回非零退出状态码时,while命令会停止执行那组命令。
2.1 while的基本格式
while命令的格式是:
while test command
do
other commands
done
while命令的关键在于所指定的test command的退出状态码必须随着循环中运行的命令而改变。如果退出状态码不发生变化, while循环就将一直不停地进行下去。
最常见的test command的用法是用方括号来检查循环命令中用到的shell变量的值。
var1=10
while [ $var1 -gt 0 ];do
echo $var1
var1=$[ $var1 -1 ]
done
$ bash test1.sh
10
9
8
7
6
5
4
3
2
1
while命令定义了每次迭代时检查的测试条件:
while [ $var1 -gt 0 ]
只要测试条件成立,while命令就会不停地循环执行定义好的命令。在这些命令中,测试条件中用到的变量必须修改,否则就会陷入无限循环。在本例中,我们用shell算术来将变量值减一:
var1=$[ $var1 - 1 ]
while循环会在测试条件不再成立时停止。
2.2 使用多个测试命令
while命令允许我们在while语句行定义多个测试命令。只有最后一个测试命令的退出状态码
会被用来决定什么时候结束循环。
var1=10
while echo $var1
[ $var1 -ge 0 ];do
echo "This is inside the loop"
var1=$[ $var1 - 1 ]
done
10
This is inside the loop
9
This is inside the loop
8This is inside the loop
7
This is inside the loop
6
This is inside the loop
5
This is inside the loop
4
This is inside the loop
3
This is inside the loop
2
This is inside the loop
1
This is inside the loop
0
This is inside the loop
-1
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循环才会停止。
3 until命令
until命令和while命令工作的方式完全相反。
until命令要求指定一个通常返回非零退出状态码的测试命令。只有测试命令的退出状态码不为0,bash shell才会执行循环中列出的命令。一旦测试命令返回了退出状态码0,循环就结束了。
until命令的格式:
until test commands; do
other commands
done
和while命令类似,我们可以在until命令语句中放入多个测试命令。只有最后一个命令的退
出状态码决定了bash shell是否执行已定义的other commands。
var1=100
until [ $var1 -eq 0 ]; do
echo $var1
var1=$[ $var1 -25 ]
done
100
75
50
25
本例中会测试var1变量来决定until循环何时停止。只要该变量的值等于0,until命令就会停止循环。
var1=100
until echo $var1
[ $var1 -eq 0 ]; do
echo Inside the loop: $var1
var1=$[ $var1 -25 ]
done
$ bash test1.sh
100
Inside the loop: 100
75
Inside the loop: 75
50
Inside the loop: 50
25
Inside the loop: 25
0
shell会执行指定的多个测试命令,只有在最后一个命令成立时停止。
4 嵌套循环
这里有个在for循环中嵌套for循环的简单例子。
for (( a=1; a<=3; a++ )); do
echo "Starting loop $a:"
for (( b=1; b<=3; b++ )); do
echo " Inside loop:$b"
done
done
$ bash test1.sh
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
这个被嵌套的循环(也称为内部循环,inner loop)会在外部循环的每次迭代中遍历一次它所
有的值。注意,两个循环的do和done命令没有任何差别。bash shell知道当第一个done命令执行时是指内部循环而非外部循环。
var1=5
while [ $var1 -ge 0 ]; do
echo "Out loop: $var1"
for (( var2=1; $var2<3; var2++ ));do
var3=$[ $var1*$var2 ]
echo " Inner loop: $var1 * $var2 = $var3"
done
var1=$[ $var1-1 ]
done
$ bash test1.sh
Out loop: 5
Inner loop: 5 * 1 = 5
Inner loop: 5 * 2 = 10
Out loop: 4
Inner loop: 4 * 1 = 4
Inner loop: 4 * 2 = 8
Out loop: 3
Inner loop: 3 * 1 = 3
Inner loop: 3 * 2 = 6
Out loop: 2
Inner loop: 2 * 1 = 2
Inner loop: 2 * 2 = 4
Out loop: 1
Inner loop: 1 * 1 = 1
Inner loop: 1 * 2 = 2
Out loop: 0
Inner loop: 0 * 1 = 0
Inner loop: 0 * 2 = 0
同样,shell能够区分开内部for循环和外部while循环各自的do和done命令。
5 循环处理文件数据
通常必须遍历存储在文件中的数据。这要求结合已经讲过的两种技术:
使用嵌套循环
修改IFS环境变量
通过修改IFS环境变量,就能强制for命令将文件中的每行都当成单独的一个条目来处理,即便数据中有空格也是如此。一旦从文件中提取出了单独的行,可能需要再次利用循环来提取行中的数据。
典型的例子是处理/etc/passwd文件中的数据。这要求我们逐行遍历/etc/passwd文件,并将IFS变量的值改成冒号,这样就能分隔开每行中的各个数据段了。
IFS.OLD=$IFS
IFS=$'\n'
for entry in $(cat /etc/passwd); do
echo "Value in $entry -"
IFS=:
for value in $entry; do
echo " $value"
done
done
这个脚本使用了两个不同的IFS值来解析数据。
第一个IFS值解析出/etc/passwd文件中的单独的行。内部for循环接着将IFS的值修改为冒号,允许我们从/etc/passwd的行中解析出单独的值。
内部循环会解析出/etc/passwd每行中的各个值。
6 控制循环
有两个命令能帮我们控制循环内部的情况:
break命令
continue命令
6.1 break命令
break命令是退出循环的一个简单方法。可以用break命令来退出任意类型的循环,包括while和until循环。
1跳出单个循环
for var1 in 1 2 3 4 5 6 7 8 9 10; do
if [ $var1 -eq 5 ]
then
break
fi
echo "Interation number: $var1"
done
echo "The for loop is completed"
$ bash test1.sh
Interation number: 1
Interation number: 2
Interation number: 3
Interation number: 4
The for loop is completed
for循环通常都会遍历列表中指定的所有值。但当满足if-then的条件时,shell会执行break命令,停止for循环。
这种方法同样适用于while和until循环。
var1=1
while[ $var1 -lt 10 ]; do
if [ $var1 -eq 5 ]
then
break
fi
echo "Interation:$var1"
var1=$[ $var1+1 ]
done
echo "The while loop is completed"
Iteration: 1
Iteration: 2
Iteration: 3
Iteration: 4
The while loop is completed
while循环会在if-then的条件满足时执行break命令,终止。
2跳出内部循环
在处理多个循环时,break命令会自动终止我们所在的最内层的循环。
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
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时执行break命令。
注意,即使内部循环通过break命令终止了,外部循环依然继续执行。
3跳出外部循环
break命令接受单个命令行参数值:
break n
其中n指定了要跳出的循环层级。默认情况下,n为1,表明跳出的是当前的循环。如果将n设为2,break命令就会停止下一级的外部循环。
for (( a=1; a<4; a++ )); do
echo "Outer loop:$a"
for (( b=1; b<100; b++ )); do
if [ $b -gt 4]
then
break2
fi
echo " Inner loop:$b"
done
done
Outer loop: 1
Inner loop: 1
Inner loop: 2
Inner loop: 3
Inner loop: 4
当shell执行了break命令后,外部循环就停止了。
6.2 continue命令
continue命令可以提前中止某次循环中的命令,但并不会完全终止整个循环。可以在循环内部设置shell不执行命令的条件。
for (( var1=1; var1 < 15; var1++ )); do
if [ $var1 -gt 5 ] && [ $var1 -lt 10 ]
then
continue
fi
echo "Interation number:$var1"
done
$ bash test1.sh
Interation number:1
Interation number:2
Interation number:3
Interation number:4
Interation number:5
Interation number:10
Interation number:11
Interation number:12
Interation number:13
Interation number:14
当if-then语句的条件被满足时(值大于5且小于10),shell会执行continue命令,跳过此
次循环中剩余的命令,但整个循环还会继续。当if-then的条件不再被满足时,一切又回到正轨。
也可以在while和until循环中使用continue命令,但要特别小心,当shell执行continue命令时,它会跳过剩余的命令。
和break命令一样,continue命令也允许通过命令行参数指定要继续执行哪一级循环:
continue n
其中n定义了要继续的循环层级。
for (( a=1; a<=5; a++ )); do
echo "Iteration $a:"
for (( b=1; b<3; b++ )); do
if [ $a -gt 2 ] && [ $a -lt 4 ]
then
continue 2
fi
var3=$[ $a * $b ]
echo " The result of $a * $b is $var3"
done
done
$ bash test1.sh
Iteration 1:
The result of 1 * 1 is 1
The result of 1 * 2 is 2
Iteration 2:
The result of 2 * 1 is 2
The result of 2 * 2 is 4
Iteration 3:
Iteration 4:
The result of 4 * 1 is 4
The result of 4 * 2 is 8
Iteration 5:
The result of 5 * 1 is 5
The result of 5 * 2 is 10
其中的if-then语句:
if [ $a -gt 2 ] && [ $a -lt 4 ]
then
continue 2
fi
此处用continue命令来停止处理循环内的命令,但会继续处理外部循环。注意,值为3的那次迭代并没有处理任何内部循环语句,因为尽管continue命令停止了处理过程,但外部循环依然会继续。
7 处理循环的输出
最后,在shell脚本中,我们可以对循环的输出使用管道或进行重定向。
这可以通过在done命令之后添加一个处理命令来实现。
for file in /home/kaoku/*; do
if [ -d "$file" ]
then
echo "$file is a directory"
elif
echo "$file is a file"
fi
done > output.txt
shell会将for命令的结果重定向到文件output.txt中,而不是显示在屏幕上。
for (( a = 1; a < 10; a++ )); do
echo "The number is $a"
done > test23.txt
echo "The command is finished."
The command is finished.
$ cat test23.txt
The number is 1
The number is 2
The number is 3
The number is 4
The number is 5
The number is 6
The number is 7
The number is 8
The number is 9
shell创建了文件test23.txt并将for命令的输出重定向到这个文件。shell在for命令之后正常显示了echo语句。
这种方法同样适用于将循环的结果管接给另一个命令。
for state in "North Dakota" Connecticut Illinois Alabama Tennessee; do
echo "$state is the next place to go"
done | sort
echo "This completes our travels"
Alabama is the next place to go
Connecticut is the next place to go
Illinois is the next place to go
North Dakota is the next place to go
Tennessee is the next place to go
This completes our travels
state值并没有在for命令列表中以特定次序列出。for命令的输出传给了sort命令,该命令会改变for命令输出结果的顺序。
8 实例
8.1 查找可执行文件
当我们从命令行中运行一个程序的时候,Linux系统会搜索一系列目录来查找对应的文件。
这些目录被定义在环境变量PATH中。如果想找出系统中有哪些可执行文件可供使用,只需要扫描PATH环境变量中所有的目录就行了。
首先是创建一个for循环,对环境变量PATH中的目录进行迭代。处理的时候别忘了设置IFS分隔符。
IFS=:
for folder in $PATH; do
现在我们已经将各个目录存放在了变量$folder中,可以使用另一个for循环来迭代特定目录中的所有文件。
for file in $folder/*; do
最后一步是检查各个文件是否具有可执行权限,可以使用if-then测试功能来实现。
if [ -x $file ]
then
echo " $file"
fi
好了,搞定了!将这些代码片段组合成脚本就行了。
IFS=:
for folder in $PATH; do
echo "$folder:"
for file in $folder/*; do
if [ -x $file ]
then
echo " $file"
fi
done
done
$ bash test1.sh | head
/root/miniconda3/condabin:
/root/miniconda3/condabin/conda
/root/miniconda3/condabin/mamba
/usr/local/sbin:
/usr/local/bin:
/usr/sbin:
/usr/sbin/aa-remove-unknown
/usr/sbin/aa-status
/usr/sbin/aa-teardown
/usr/sbin/accessdb
运行这段代码时,我们会得到一个可以在命令行中使用的可执行文件的列表。
以上就是结构化命令的知识补充,让我们继续Linux实战处理学习吧。
我们下一篇再见!
网友评论