More-Structured-Commands
# 本章内容
- Looping with the
forstatement - Using the
whilestatement - Using the
untilstatement - Nested loops
- Controlling the loops
- Redirecting and piping loop
# Looking with the for Command
for 在 shell script 中的语法为:
for var in list
do
commands
done
2
3
4
或者写为:
for var in list; do
commands
done
2
3
# Reading values in a list
for 最常见的使用方法是迭代遍历 list ,举例如下:
(注意 list 中不同的值用默认用空格分隔,详情看 IFS,如果同一个值中间有空格,比如 New York,需要加 double quotation,即 "New York")
$ cat script.sh
#!/bin/bash
# basic for command
for test in Alabama Alaska Arizona Arkansas California Colorado
do
echo The next state is $test
done%
$ ./script.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
2
3
4
5
6
7
8
9
10
11
12
13
14
遍历结束后,上面例子中的 test 变量不会消失仍可继续使用 (这和大部分的编程语法不同),它会存储 list 中的最后一个值,一直到 script 运行结束;当然也可以在遍历结束后修改 test 变量的值。
$ cat script.sh
#!/bin/bash
# basic for command
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're visiting $test"%
$ ./script.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're visiting Connecticut
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Reading complex values in a list
single quotation 中的字符串在 list 中会被解释为一个没有分隔的整体:
$ cat script.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%
$ ./script.sh
word:I
word:dont know if thisll
word:work
2
3
4
5
6
7
8
9
10
解决办法:
- 可以使用 backslash 来 escape single quotation
- 也可以使用 double quotation 将 single quotation 括起来
$ cat script.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%
$ ./script.sh
word:I
word:don't
word:know
word:if
word:this'll
word:work
2
3
4
5
6
7
8
9
10
11
12
13
# Reading a list from a variable
list 除了可以是不同 item 的组合之外,list 也可以是一个变量,如下面的例子所示:
#!/bin/bash
# using a variable to hold the list
list="Alabama Alaska Arizona Arkansas Colorado"
# 将一个新的item添加到list中的方法
list=$list" Connecticut"
for state in $list
do
echo "Have you ever visited $state?"
done
2
3
4
5
6
7
8
9
提示
这里值得注意的是,往一个 list 变量中加入一个新的 item 的方法: list=$list" Connecticut"
# Reading values from a command
list 也可以是使用 command substitution 读取的内容
# space, newline都是默认的field separator,具体看下一节
$ cat states.txt
Alabama
Alaska
Arizona
Arkansas
Colorado
Connecticut Delaware Florida Georgia
$ cat script.sh
#!/bin/bash
# using a variable to hold the list
file="states.txt"
# using command substitution
for state in $(cat $file)
do
echo "Have you ever visited $state?"
done
$ ./script.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?
Have you ever visited Delaware?
Have you ever visited Florida?
Have you ever visited Georgia?
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Changing the field separator
IFS (Internal Field Seperator): IFS 中定义了所有 field separators 的所有字符,bash shell 将用这些字符作为分隔符。
默认的 IFS 有:newline, space, tab
可以通过临时的改变 IFS 使得 for 读取 list 的 item 发生改变:
$ cat states.txt
Alabama
Alaska
Arizona
Arkansas
Colorado
Connecticut Delaware Florida Georgia
$ cat script.sh
#!/bin/bash
# using a variable to hold the list
file="states.txt"
# 改变了分隔符只有newline
IFS=$'\n'
for state in $(cat $file)
do
echo "Have you ever visited $state?"
done
$ ./script.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 Delaware Florida Georgia?
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
注意
A safe practice to get into is to save the originalIFS value before changing it and then restore it when you're finished:
IFS.OLD=\$IFS
IFS=\$'\n'
<use the new IFS value in code>
IFS=$IFS.OLD
2
3
4
如果需要添加多个 IFS,直接把它们放一起即可:
IFS=$'\n':;"
(使用 newline , : , : , " 作为 IFS)
# Reading a directory using wildcards
$ cat script.sh
#!/bin/bash
# iterate through all the files in directory '/'
# list中还可以有多个文件或者文件夹路径
for file in /* /home/tartarus/badtest
do
# $file需要用double quotation注释,因为有些文件名中可能有space,错误看下一个例子
if [ -d "$file" ]
then
echo "$file is a directory"
elif [ -f "$file" ]
then
echo "$file is a file"
fi
done
$ ./script.sh
/bin is a directory
/boot is a directory
/cdrom is a directory
/dev is a directory
/etc is a directory
...
/home/tartarus/badtest doesn't exist
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
读取上面例子中的 file 变量时 ( if [ -d "$file" ] ),最好使用将读取的值放到 double quotation 中,否则可能会因为文件名中有空格而产生错误:
$ ls -r demo
'hello world'
$ cat script.sh
#!/bin/bash
# iterate through all the files in a directory
for file in /tmp/demo/*
do
if [ -d $file ]
then
echo "$file is a directory"
elif [ -f $file ]
then
echo "$file is a file"
fi
done
$ ./script.sh
./script.sh: line 5: [: /tmp/demo/hello: binary operator expected
./script.sh: line 8: [: /tmp/demo/hello: binary operator expected
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Trying the C-Style for Command
在 Bash shell 中也可以使用 C 风格的 for 循环:
for (( variable assignment ; condition ; iteration process ))
举例:
$ cat script.sh
#!/bin/bash
# testing the C-style for loop
for (( i=1; i <= 10; i++ ))
do
echo "The next number is $i"
done
$ ./script.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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
提示
C 风格 for 循环的双括号中:
- 变量赋值语句的等号左右可以有空格
- 使用变量时也不需要加 dollar sign
- 数学运算也不需要使用
expr命令格式
# Using multiple variables
C 风格的 for 循环中还可以使用多个变量,但是条件判断只能有一个变量:
$ cat script.sh
#!/bin/bash
# multiple variables: 可以有多个变量,但是条件判断时只能有一个变量
for (( a=1, b=10; a <= 10; a++, b-- ))
do
echo "$a - $b"
done%
$ ./script.sh
1 - 10
2 - 9
3 - 8
4 - 7
5 - 6
6 - 5
7 - 4
8 - 3
9 - 2
10 - 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Using the while Command
while 语句判断 command 是否运行成功,如果退出码为 0,则执行循环中的命令。
语法:
while command # 也可以使用 while [ condition ]
do
other commands
done
2
3
4
注意
while 循环的关键是状态码,如果状态码一直为 0,那么 while 循环就变成了死循环。
其中,常把 command 设置一个 test 的判断条件, test 命令判断 condition 是否成立,如果成立状态码为 0,执行 commands ; 否则退出循环。
while test condition # 也可以使用 while [ condition ]
do
other commands
done
2
3
4
举例:
$ cat script.sh
#!/bin/bash
# while command test
var1=10
while [ $var1 -gt 0 ] // 等价于: test $var1 -gt 0
do
echo $var1
var1=$[ $var1 - 1 ]
done%
$ ./script.sh
10
9
8
7
6
5
4
3
2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
提示
while 中可以有多条 command,但是只由最后命令最好是一个 test conditon 来决定继续循环还是退出,每条命令都要占一行。
举例:
$ cat script.sh
#!/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
$ ./script.sh
10
This is inside the loop
9
This is inside the loop
8
This 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Using the until Command
until 的使用方式和 while 恰好相反。 如果 commands 中的最后一条命令的状态码返回为 0,则退出循环,否则进行循环。
until 语法:
until commands
do
commands
done
2
3
4
举例:
$ cat script.sh
#!/bin/bash
# using the until command
var1=100
until echo $var1
[ $var1 -eq 0 ]
do
echo Inside the loop: $var1
var1=$[ $var1 - 25 ]
done
$ ./script.sh
100
Inside the loop: 100
75
Inside the loop: 75
50
Inside the loop: 50
25
Inside the loop: 25
0
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Nested Loop
无论是 C 风格的 for 循环,还是 while, until 循环,都可以嵌套使用。但是嵌套使用容易引起错误。
举例:使用嵌套的 for 循环读取文件 etc/passwd
IFS_old=$IFS
IFS=$'\n'
for entry in $(cat /etc/passwd)
do
IFS=$:
echo "Values in $entry –"
for item in $entry
do
echo $item
done
done
IFS=$IFS_old
$ ./script.sh
Values in root:x:0:0:root:/root:/bin/bash –
root
x
0
0
root
/root
/bin/bash
Values in daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin –
daemon
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Controlling the Loop
Bash 脚本中也可以使用 break 和 continue 两个字段来控制循环:
breakbreak可以使用于任意类型的循环 (for,while,until)
$ cat script.sh #!/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"% $ ./script.sh Iteration number: 1 Iteration number: 2 Iteration number: 3 Iteration number: 4 The for loop is completed1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19break n可以跳出 n 个循环
$ cat script.sh #!/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 $ ./script.sh Outer loop: 1 Inner loop: 1 Inner loop: 2 Inner loop: 3 Inner loop: 41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22continue可以使用于任意类型的循环 (for,while,until)
$ cat script.sh
#!/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
$ ./script.sh
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
continue n(跳过一个循环的 n 次遍历)
$ cat script.sh
#!/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
$ ./script.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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Redirecting and Piping Loop
可以将一个循环的输出进行重定向或者 pipe。
- 将 for 循环到输出重定向到一个文件中
#/bin/bash
for file in /home/tartarus/*
do
if [ -d "$file" ]
then
echo "$file is a directory"
else
echo "$file is a file"
fi
done > output.txt
2
3
4
5
6
7
8
9
10
- 将一个文件作为输出重定向到 while 循环中
read可以将一个文件中的一行作为输入读取到一个或者多个变量中
$ cat users.csv
rich,Richard Blum
christine,Christine Bresnahan
barbara,Barbara Blum
tim,Timothy Bresnahan
$ cat script.sh
#/bin/bash
# 这里使用了读取文件到循环中的一个tricky
while IFS=',' read loginname fullname
do
echo "Loginname:$loginname Fullname:$fullname";
done < users.csv
$ ./script.sh
Loginname:rich Fullname:Richard Blum
Loginname:christine Fullname:Christine Bresnahan
Loginname:barbara Fullname:Barbara Blum
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 将 for 循环到输出作为管道传递给另一个命令作为输入
#/bin/bash
for file in /home/tartarus/*
do
if [ -d "$file" ]
then
echo "$file is a directory"
else
echo "$file is a file"
fi
done | sort
2
3
4
5
6
7
8
9
10