第三章 更多的结构化命令
13.1 for命令
bash shell提供了for
命令,允许你创建一个遍历一系列值的循环。每次迭代都是用其中一个值来执行已定义好的命令。
//for语法
for var in list
do
commands
done
在每次迭代中,变量var
会包含列表中的当前值。第一次迭代会使用列表中的第一个值,第二次迭代使用第二个值,以此类推,直到列表中的所有值都过一遍。
13.1.1 读取列表中的值
#遍历列表中的所有值
$ cat test1
#!/bin/bash
# basic for command
for test in Alabama Alaska Arizona Arkansas California Colorado
do
echo The next state is $test
done
$ ./test1
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
13.1.2 读取列表中的复杂值
#带有单引号会使单词分隔失败
$ cat badtest1
#!/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
$ ./badtest1
word:I
word:dont know if thisll
word:work
有两种方法解决此问题:
- 使用转义字符
\
来将单引号转义 - 使用双引号来定义用到单引号的值
$ cat test2
#!/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
$ ./test2
word:I
word:don't
word:know
word:if
word:this'll
word:work
for命令用空格来划分列表的每个值。如果在单独的数据值中有空格,就必须使用双引号将这些值圈起来。在某个值两边使用双引号时,shell并不会将双引号当成值的一部分。
3.1.3 从变量读取列表
$ cat test4
#!/bin/bash
# using a variable to hold the list
#使用list存储列表
list="Alabama Alaska Arizona Arkansas Colorado"
#在list变量末尾添加变量“Connecticut”
list=$list" Connecticut"
for state in $list
do
echo "Have you ever visited $state?"
done
$ ./test4
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?
3.1.4 从命令读取值
$ cat test5
#!/bin/bash
# reading values from a file
file="states"
#使用cat命令来输出文件states的内容。本示例中文件state与脚本test5在同一个文件夹中,如果不是,需要使用全路径名(绝对路径或相对路径)来引用文件位置
for state in $(cat $file)
do
echo "Visit beautiful $state"
done
$ cat states
Alabama
Alaska
Arizona
$ ./test5
Visit beautiful Alabama
Visit beautiful Alaska
Visit beautiful Arizona
3.1.5 更改字段分隔符
换将变量IFS
,叫做内部字段分隔符(internal field separator),IFS
环境变量定义了bash shell
用作字段分隔符的一系列字符。默认情况下,bash shell
会将下列字符当做字段分隔符:
- 空格
- 制表符
- 换行符
bash shell在数据中看到了这些字符中的任意一个,它就会假定这表明了列表中一个新数据字段的开始。
#使用以下语句设定bash shell只识别换行符作为新数据将字段的开始,bash shell会忽略空格和制表符
IFS = $'\n'
在处理代码量较大的脚本时,可能在一个地方需要修改IFS的值,然后忽略这次修改,在脚本的其他地方继续沿用IFS
的默认值。可以在改变IFS之间保存原来的IFS
值,之后再恢复它。
IFS.OLD = $IFS
IFS = $'\n'
#省略代码块
...
IFS = $IFS.OLD
如果需要指定多个IFS字符,只要将它们在赋值行串起来就行。
#这个赋值会将换行符,冒号,分号和双引号作为字段分隔符
IFS = $'\n':;"
3.1.6 用通配符读取目录
使用for
命令来自动遍历目录中的文件。进行此操作时,必须在文件或路径名中使用通配符。他会强制shell使用文件拓展匹配。文件拓展匹配是生成匹配指定通配符的文件名或路径名的过程。
$ cat test6
#!/bin/bash
# iterate through all the files in a directory
for file in /home/rich/test/*
do
#Linux中,目录名和文件名中包含空格是合法的,所以应该使用是双引号将“$file”圈起来
if [ -d "$file" ]
then
echo "$file is a directory"
elif [ -f "$file" ]
then
echo "$file is a file"
fi
done
$ ./test6
/home/rich/test/dir1 is a directory
/home/rich/test/myprog.c is a file
/home/rich/test/myprog is a file
/home/rich/test/myscript is a file
/home/rich/test/newdir is a directory
3.2 C语言风格的for命令
3.2.1 C语言的for
循环
for(i=0; i<10; i++)
{
printf("The next number is %0d\n", i);
}
3.2.2 bash中C语言风格的for
循环
for((variable assignment; condition; iteration process))
bash中C语言风格的循环中,变量的引用不使用$
符号,与单独引用变量不同;同时变量迭代的过程不使用expr
命令格式。
$ cat test8
#!/bin/bash
# testing the C-style for loop
for (( i=1; i <= 3; i++ ))
do
echo "The next number is $i"
done
$ ./test8
The next number is 1
The next number is 2
The next number is 3
3.2.3 使用多个变量
bash中C语言风格的for
循环允许为迭代使用多个变量,循环会单独处理每个变量。
$ cat test9
#!/bin/bash
# multiple variables
for (( a=1, b=10; a <= 3; a++, b-- ))
do
echo "$a - $b"
done
$ ./test9
1 - 10
2 - 9
3 - 8
3.3 while命令
3.3.1 while命令
while
命令某种意义上是if-then
语句和for
循环的混合体。while
命令定义一个要测试的命令,然后循环执行一组命令,只要定义的测试命令返回的是退出状态码0。while
会在每次迭代的一开始测试test
命令,在test
命令返回非零退出码时,while
命令会停止执行那组命令。
#while的基本格式
while test command
do
other commands
done
while
命令中定义的test
,command
和if-then
语句中的格式一模一样。可以使用任何普通的的bash shell命令,或者用test
命令进行条件测试。while
命令的关键在于所指定的test command
的退出状态码必须随着循环中运行的命令而改变。如果退出状态码不发生变化,while
循环就将一直不停地进行下去。最常见的test command
用法是用方括号来检查循环命令中用到的shell
变量的值。
$ cat test10
#!/bin/bash
# while command test
var1=10
while [ $var1 -gt 7 ]
do
echo $var1
var1=$[ $var1 - 1 ]
done
$ ./test10
10
9
8
3.3.2 使用多个测试命令
while
命令允许你在while
语句行定义多个测试命令。只有最后一个测试命令的退出状态码会被用来决定什么时候结束循环。
$ cat test11
#!/bin/bash
# testing a multicommand while loop
var1=10
while echo $var1
[ $var1 -ge 0 ]
do
echo "This is inside the loop"
var1=$[ $var1 - 1 ]
done
$ ./test11
10
This is inside the loop
9
This is inside the loop
...
This is inside the loop
-1
$
最后var1
值为0时,继续执行循环,循环体使var1
值继续减一,进入判断打印出var1
的值为-1,终止循环。
3.4 until命令
until
命令和while
命令工作的方式完全相反。until
命令要求你指定一个通常返回非零退出状态码的测试命令。只有测试命令的退出状态码不为0,bash shell才会执行循环中列出的命令。一旦测试命令返回了退出状态码0,循环就结束了。
until test commands
do
other commands
done
和while
命令类似,可以在until
命令语句中放入多个测试命令。只有最后一个命令的退出状态码决定了bash shell是否执行已定义的other commands
。
$ cat test12
#!/bin/bash
# using the until command
var1=100
until [ $var1 -eq 0 ]
do
echo $var1
var1=$[ $var1 - 25 ]
done
$ ./test12
100
75
50
25
$
shell会执行指定的多个测试命令,只有在最后一个命令成立时停止。
$ cat test13
#!/bin/bash
# using the until command
var1=100
until echo $var1
[ $var1 -eq 0 ]
do
echo Inside the loop: $var1
var1=$[ $var1 - 25 ]
done
$ ./test13
100
Inside the loop: 100
75
Inside the loop: 75
50
Inside the loop: 50
25
Inside the loop: 25
0
$
3.5 嵌套循环
循环语句中可以使用任意类型的命令,包括其他循环,这种循环叫做嵌套循环(nested loop)。在使用嵌套循环时,是使用迭代中使用迭代,与命令运行的次数是乘积关系。
$ cat test14
#!/bin/bash
# nesting for loops
for (( a = 1; a <= 3; a++ ))
do
echo "Starting loop $a:"
for (( b = 1; b <= 3; b++ ))
do
echo " Inside loop: $b"
done
done
$ ./test14
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
$
内部循环会在外部循环的每次迭代中遍历一次它所有的值。
还可以混用其他循环,如while
和for
循环,until
和while
循环。
3.6 循环处理文件数据
遍历存储文件中的数据需要使用两种技术:
- 使用嵌套循环
- 修改IFS环境变量
通过修改IFS
环境变量,就能强制for
命令将文件中的每行都当成单独的一个条目来处理,即使数据中有空格也是如此。
#!/bin/bash
# changing the IFS value
IFS.OLD=$IFS
IFS=$'\n'
for entry in $(cat /etc/passwd)
do
echo "Values in $entry –"
IFS=:
for value in $entry
do
echo " $value"
done
done
$
这个脚本使用了两个不同的IFS
值来解析数据,第一个IFS
值解析出/etc/passwd
文件中的单独行。内部for
循环接着将IFS
的值修改为冒号,允许你从/etc/passwd
的行中解析出单独的值。上面示例脚本输出为:
Values in rich:x:501:501:Rich Blum:/home/rich:/bin/bash -
rich
x
501
501
Rich Blum
/home/rich
/bin/bash
Values in katie:x:502:502:Katie Blum:/home/katie:/bin/bash -
katie
x
506
509
Katie Blum
/home/katie
/bin/bash
3.7 控制循环
控制循环有两个命令:
- break命令
- continue命令
3.7.1 break命令
break
命令是退出循环的一个简单方法, 可以用break
命令来退出任意类型的循环,包括while
和until
循环。
跳出单个循环
$ cat test17
#!/bin/bash
# breaking out of a for loop
for var1 in 1 2 3 4 5 6 7 8 9 10
do
if [ $var1 -eq 5 ]
then
break
fi
echo "Iteration number: $var1"
done
echo "The for loop is completed"
$ ./test17
Iteration number: 1
Iteration number: 2
Iteration number: 3
Iteration number: 4
The for loop is completed
$
跳出内部循环
在处理多个循环时,break
命令会自动终止你所在的最内层的循环。
$ cat test19
#!/bin/bash
# breaking out of an inner loop
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
跳出外部循环
break
命令接受单个命令行参数值
break n
其中n指定了要跳出的循环层级。默认情况下n
为1,表明跳出的是当前的循环。如果将n
设为2,break
命令就会停止下一级的外部循环。
$ cat test20
#!/bin/bash
# breaking out of an outer loop
for (( a = 1; a < 4; a++ ))
do
echo "Outer loop: $a"
for (( b = 1; b < 100; b++ ))
do
if [ $b -gt 4 ]
then
break 2
fi
echo " Inner loop: $b"
done
done
$ ./test20
Outer loop: 1
Inner loop: 1
Inner loop: 2
Inner loop: 3
Inner loop: 4
3.7.2 continue命令
continue
命令可以提前中止某次循环中的命令,但并不会完全终止整个循环。
$ cat test21
#!/bin/bash
# using the continue command
for (( var1 = 1; var1 < 15; var1++ ))
do
if [ $var1 -gt 5 ] && [ $var1 -lt 10 ]
then
continue
fi
echo "Iteration number: $var1"
done
$ ./test21
Iteration number: 1
Iteration number: 2
Iteration number: 3
Iteration number: 4
Iteration number: 5
Iteration number: 10
Iteration number: 11
Iteration number: 12
Iteration number: 13
Iteration number: 14
如果var1
大于并且小于10则调过后面打印命令开始递增进行下次循环判断。
也可以while
和until
循环中使用continue
命令。
和break
命令一样,continue
命令也允许向通过命令行参数指定要继续执行哪一级循环:
continue n
其中n定义了要继续的循环层级。
$ cat test22
#!/bin/bash
# continuing an outer loop
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
$ ./test22
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
$
当a
值为3时,跳至外部循环,a
值自增1,进行下次循环条件判断。
3.8 处理循环的输出
在shell脚本中,可以对循环的输出使用管道或进行重定向。可以通过在done
命令之后添加一个处理命令来实现。
for file in /home/rich/*
do
if [ -d "$file" ]
then
echo "$file is a directory"
elif
echo "$file is a file"
fi
done > output.txt
shell会将for
命令的结果重定向到文件output.txt
中,而不是显示在屏幕上。
$ cat test23
#!/bin/bash
# redirecting the for output to a file
for (( a = 1; a < 10; a++ ))
do
echo "The number is $a"
#shell创建了文件test123.txt并将for命令的输出重定向到这个文件
done > test23.txt
#shell在for命令之后正常显示了echo语句
echo "The command is finished."
$ ./test23
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
还可以将循环的结果使用管道命令给另一个命令。
$ cat test24
#!/bin/bash
# piping a loop to another command
for state in "North Dakota" Connecticut Illinois Alabama Tennessee
do
echo "$state is the next place to go"
#sort命令会为for循环的输出结果排序
done | sort
echo "This completes our travels"
$ ./test24
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
3.9 实例
循环是对系统数据进行迭代的常用方法,无论是目录中的文件还是文件中的数据。
3.9.1 查找可执行文件
当从命令行中运行一个程序时,Linux系统会搜索一些目录来查找对应的文件。这些目录被定义在环境变量PATH
中。如果你想找出系统中有哪些可执行文件,只需要扫描PATH
环境变量中的所有目录就可以了。
$ cat test25
#!/bin/bash
# finding files in the PATH
#定义IFS分隔符
IFS=:
#获取所有PATH中的目录到folder中
for folder in $PATH
do
echo "$folder:"
#对每个目录中的文件进行遍历
for file in $folder/*
do
#如果具有可执行权限,打印
if [ -x $file ]
then
echo " $file"
fi
done
done
$ ./test25 | more
/usr/local/bin:
/usr/bin:
/usr/bin/Mail
/usr/bin/Thunar
/usr/bin/X
...
3.9.2 创建多个用户账户
创建多个用户时,手动使用useradd
命令效率太低,可以将需要添加的新用户账户放在一个文本文件中,然后创建一个简单的脚本进行处理。文本的格式为:
userid, username
第一个为创建新用户的ID
,第二个是用户的名称,两个值之间使用逗号分隔,这样就形成了一种名为逗号分隔值的文件格式(或者是.csv)。
$ cat test26
#!/bin/bash
# process new user accounts
#$input变量指向数据文件
input="users.csv"
#read命令会自动读取.csv文本文件的下一行内容,所以不需要专门写一个循环来处理,当read命令返回FALSE时(也就是读取整个文件时),while命令就会自动退出
while IFS=',' read -r userid name
do
echo "adding $userid"
useradd -c "$name" -m $userid
#$input作为while命令的重定向数据
done < "$input"
$ cat users.csv
rich,Richard Blum
christine,Christine Bresnahan
barbara,Barbara Blum
tim,Timothy Bresnahan
#必须作为root用户才能运行这个脚本,因为useradd命令需要root权限
# ./test26
adding rich
adding christine
adding barbara
adding tim
本章小结
介绍了shell中循环相关的命令
1.for命令
2.while命令
3.until命令
4.循环控制命令break,continue
5.循环输入输出重定向
6.循环相关的两个实例
参考文献:Linux命令行与shell脚本编程大全(第三版)Richard Blum Christine Bresnahan著 门佳 武海峰译