本部分内容参考自《Linux命令行与shell脚本编程大全 第3版》。
for命令
重复执行一系列命令在编程中很常见。通常你需要重复一组命令直至达到某个特定条件,比如处理某个目录下的所有文件、系统上的所有用户或是某个文本文件中的所有行。
bash shell 提供了 for
命令,允许你创建一个遍历一系列值的循环。每次迭代都使用其中一个值来执行已定义好的一组命令。下面是 bash shell 中 for
命令的基本格式。
for var in list
do
commands
done
在 list
参数中,你需要提供迭代中要用到的一系列值。可以通过几种不同的方法指定列表中的值。
在每次迭代中,变量 var
会包含列表中的当前值。第一次迭代会使用列表中的第一个值,第二次迭代使用第二个值,以此类推,直到列表中的所有值都过一遍。
在 do
和 done
语句之间输入的命令可以是一条或多条标准的 bash shell 命令。在这些命令中,$var
变量包含着这次迭代对应的当前列表项中的值。
更改字段分隔符
现有包含如下内容的一个文本文件:
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
我们可以使用 for
循环输出该文件的内容:
$ cat test18.sh
#!/bin/bash
file="test1.txt"
for state in $(cat $file)
do
echo $state
done
结果如下:
$ ./test18.sh
Visit
beautiful
Alabama
Visit
beautiful
Alaska
Visit
...
North
Carolina
可以看到 for
循环默认以空格为分隔符,遍历输出了每一个单词,那如果我们想要每次输出一行的内容该怎么做?
这里有一个特殊的环境变量 IFS
,叫作内部字段分隔符(internal field separator)。IFS
环境变量定义了 bash shell 用作字段分隔符的一系列字符。默认情况下, bash shell 会将下列字符当作字段分隔符:
- 空格
- 制表符
- 换行符
如果 bash shell 在数据中看到了这些字符中的任意一个,它就会假定这表明了列表中一个新数据字段的开始。
要解决这个问题,可以在 Shell 脚本中临时更改 IFS
环境变量的值来限制被 bash shell 当作字段分隔符的字符。例如,如果你想修改 IFS
的值,使其只能识别换行符,那就必须这么做:
IFS=$'\n'
将这个语句加入到脚本中,告诉 bash shell 在数据值中忽略空格和制表符。对前一个脚本使用这种方法,将获得如下输出。
$ cat test18.sh
#!/bin/bash
file="test1.txt"
IFS=$'\n'
for state in $(cat $file)
do
echo $state
done
$ ./test18.sh
Visit beautiful Alabama
Visit beautiful Alaska
Visit beautiful Arizona
...
Visit beautiful New Hampshire
Visit beautiful North Carolina
现在, shell 脚本旧能够使用列表中含有空格的值了。
还有其他一些 IFS
环境变量的绝妙用法。假定你要遍历一个文件中用冒号分隔的值(比如在 /etc/passwd
文件中)。你要做的就是将 IFS
的值设为冒号。
IFS=:
如果要指定多个 IFS 字符,只要将它们在赋值行串起来就行。
IFS=$'\n':;"
这个赋值会将换行符、冒号、分号和双引号作为字段分隔符。如何使用 IFS 字符解析数据没有任何限制。
C风格的for命令
如果你从事过 C 语言编程,可能会对 bash shell 中 for 命令的工作方式有点惊奇。在 C 语言中,for 循环通常定义一个变量,然后这个变量会在每次迭代时自动改变。通常程序员会将这个变量用作计数器,并在每次迭代中让计数器增一或减一。bash 的 for 命令也提供了这个功能。
bash shell 也支持一种 for 循环,它看起来跟 C 语言风格的 for 循环类似,但有一些细微的不同,
其中包括一些让 shell 脚本程序员困惑的东西。以下是 bash 中 C 语言风格的 for 循环的基本格式。
for (( variable assignment ; condition ; iteration process ))
C 语言风格的 for 循环的格式会让 bash shell 脚本程序员摸不着头脑,因为它使用了 C 语言风格的变量引用方式而不是 shell 风格的变量引用方式。 C 语言风格的 for 命令看起来如下。
for (( a = 1; a < 10; a++ ))
注意,有些部分并没有遵循 bash shell 标准的 for 命令:
- 变量赋值可以有空格;
- 条件中的变量不以美元符开头;
- 迭代过程的算式未用expr命令格式;
看如下示例:
$ cat test1
#!/bin/bash
for (( i=1; i <= 10; i++ ))
do
echo "The next number is $i"
done
$ ./test1
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 循环通过定义好的变量(本例中是变量 i
)来迭代执行这些命令。在每次迭代中,$i
变量包含了 for 循环中赋予的值。在每次迭代后,循环的迭代过程会作用在变量上,在本例中,变量增一。
while命令
while
命令某种意义上是 if-then
语句和 for 循环的混杂体。 while
命令允许定义一个要测试的命令,然后循环执行一组命令,只要定义的测试命令返回的是退出状态码 0。它会在每次迭代的一开始测试 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 test1.sh
#!/bin/bash
var1=10
while [ $var1 -gt 0 ]
do
echo $var1
var1=$[ $var1 - 1 ]
done
$ ./test1.sh
10
9
8
7
6
5
4
3
2
1
while
命令定义了每次迭代时检查的测试条件:
while [ $var1 -gt 0 ]
只要测试条件成立,while 命令就会不停地循环执行定义好的命令。在这些命令中,测试条件中用到的变量必须修改,否则就会陷入无限循环。在本例中,我们用 shell 算术来将变量值减一:
var1=$[ $var1 - 1 ]
while 循环会在测试条件不再成立时停止。
while循环的特殊用法
while
循环可以很方便的遍历文件的每一行,语法如下:
while read line;do
循环体
done < file
该语法示例的含义就是从 file
文件中每次读一行,每一行的内容保存到 line
变量。
例:输出/etc/passwd
文件中 id 为偶数的用户的 id 和用户名。
#!/bin/bash
IFS=":";
while read line;do
echo $line;
done < /etc/passwd
until命令
until
命令和 while
命令工作的方式完全相反。 until
命令要求你指定一个通常返回非零退出状态码的测试命令。只有测试命令的退出状态码不为 0,bash shell 才会执行循环中列出的命令。一旦测试命令返回了退出状态码 0,循环就结束了。
和你想的一样, until
命令的格式如下。
until test commands
do
other commands
done
和 while
命令类似,你可以在 until
命令语句中放入多个测试命令。只有最后一个命令的退出状态码决定了 bash shell 是否执行已定义的 other commands
。
下面是使用 until
命令的一个例子。
$ cat test2.sh
#!/bin/bash
var1=100
until [ $var1 -eq 0 ]
do
echo $var1
var1=$[ $var1 - 25 ]
done
$ ./test2.sh
100
75
50
25
本例中会测试 var1
变量来决定 until
循环何时停止。只要该变量的值等于 0, until
命令就会停止循环。
控制循环
你可能会想,一旦启动了循环,就必须苦等到循环完成所有的迭代。并不是这样的。有两个命令能帮我们控制循环内部的情况:
break
命令;continue
命令;
每个命令在如何控制循环的执行方面有不同的用法。
break命令
break
命令是退出循环的一个简单方法。可以用 break
命令来退出任意类型的循环,包括 while
和 until
循环。
跳出单个循环
在 shell 执行 break
命令时,它会尝试跳出当前正在执行的循环。
$ cat test3.sh
#!/bin/bash
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"
$ ./test3.sh
Iteration number: 1
Iteration number: 2
Iteration number: 3
Iteration number: 4
The for loop is completed
for 循环通常都会遍历列表中指定的所有值。但当满足 if-then
的条件时, shell 会执行 break
命令,停止 for 循环。
这种方法同样适用于
while
和until
循环。
跳出内部循环
在处理多个循环时, break
命令会自动终止你所在的最内层的循环。
$ cat test4.sh
#!/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
$ ./test4.sh
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
命令终止了,外部循环依然继续执行。
跳出外部循环
有时你在内部循环,但需要停止外部循环。 break
命令接受单个命令行参数值:
break n
其中 n
指定了要跳出的循环层级。默认情况下, n
为 1
,表明跳出的是当前的循环。如果你将 n
设为 2
,break
命令就会停止下一级的外部循环。
$ cat test5.sh
#!/bin/bash
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
$ ./test5.sh
Outer loop: 1
Inner loop: 1
Inner loop: 2
Inner loop: 3
Inner loop: 4
注意,当 shell 执行了 break
命令后,外部循环就停止了。
continue命令
continue
命令可以提前中止某次循环中的命令,但并不会完全终止整个循环。可以在循环内部设置 shell 不执行命令的条件。这里有个在 for 循环中使用 continue
命令的简单例子。
$ cat test5.sh
#!/bin/bash
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
$ ./test5.sh
Outer loop: 1
Inner loop: 1
Inner loop: 2
Inner loop: 3
Inner loop: 4
当 if-then
语句的条件被满足时(值大于 5 且小于 10), shell 会执行 continue
命令,跳过此次循环中剩余的命令,但整个循环还会继续。当 if-then
的条件不再被满足时,一切又回到正轨。
也可以在 while
和 until
循环中使用 continue
命令,但要特别小心。记住,当 shell 执行 continue
命令时,它会跳过剩余的命令。
处理循环的输出
最后,在 shell 脚本中,你可以对循环的输出使用管道或进行重定向。这可以通过在 done
命令之后添加一个处理命令来实现。
$ cat test6.sh
#!/bin/bash
for file in /home/*
do
if [ -d "$file" ]
then
echo "$file is a directory"
else
echo "$file is a file"
fi
done > output.txt
$ ./test6.sh
$ cat output.txt
/home/docker is a directory
/home/user1 is a directory
shell 会将 for
命令的结果重定向到文件 output.txt
中,而不是显示在屏幕上。
这种方法同样适用于将循环的结果管接给另一个命令。
$ cat test7.sh
#!/bin/bash
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"
$ ./test7.sh
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 命令输出结果的顺序。运行这个脚本实际上说明了结果已经在脚本内部排好序了。
示例
for九九乘法表
#!/bin/bash
for ((i=1;i<=9;i++));do
for((j=1;j<=i;j++));do
echo -ne "$j * $i = $[$i * $j]\t"
done
echo
done
while九九乘法表
#!/bin/bash
declare -i i=1;
declare -i j=1;
while [ $i -lt 10 ];do
while [ $j -le $i ];do
echo -ne "$j * $i = $[$j * $i]\t";
let j++
done
echo
let j=1;
let i++
done
生成10个随机数取最大值和最小值
#!/bin/bash
min=0;
max=0;
loopCount=0
while [ $loopCount -lt 10 ];do
random=`echo $RANDOM`
if [ $loopCount -eq 0 ];then
min=$random;
else
if [ $random -lt $min ];then
min=$random
elif [ $random -gt $max ];then
max=$random
fi
fi
let loopCount++
echo "$[$loopCount]:$random"
done
echo "max:$max"
echo "min:$min"
until九九乘法表
#!/bin/bash
i=1
j=1
until [ $i -gt 9 ];do
until [ $j -gt $i ];do
echo -ne "$j X $i = $[$j * $i]\t"
let j++
done
let j=1;
let i++;
echo
done
求1到100偶数的和
#!/bin/bash
sum=0
for i in {1..100};do
if [ $[$i % 2] -eq 1 ];then
continue;
fi
sum=$[$sum+$i];
done
echo $sum;
每隔3秒检测用户是否登录
使用 while
实现:
read -p '指定用户名:' username;
checkCount=0
while true; do
let checkCount++;
if who | grep "$username" &> /dev/null;then
break;
fi
echo "检查第 $checkCount 次"
sleep 3;
done
echo "用户 $username 已登录" >> /tmp/log.txt;
使用 until
实现:
#!/bin/bash
read -p '指定用户名:' username;
checkCount=0
until who | grep "$username" &> /dev/null; do
let checkCount++;
echo "检查第 $checkCount 次"
sleep 3;
done
echo "用户 $username 已登录" >> /tmp/log.txt;
评论区