接上一篇文章Linux shell编程(六): 基本shell脚本
3.2 使用结构化命令
前面介绍的都是顺序执行的命令,有时需要按照逻辑顺序执行命令,这是就需要对命令命令施加一些逻辑流程控制,这样的命令通常称为结构化命令。
3.2.1 判断语句
if-then
最基本的结构化命令就是 if-then 语句。格式如下:
if command
then
commands
fi
与大多数编程语言的 if 语句不同,bash shell 的 if
后面不是判断条件语句的真假,而是当 if
后面的 command
的退出状态码为 0
时,位于 then
部分的 commands
就会被执行,fi
语句表示 if-then
语句到此结束。
有时为了使 bash shell 的 if-then 语句看起来更舒服一点,也可以把 then
和 if
语句放在同一行,并用 ;
隔开,如下:
if command; then
commands
fi
if-then-else
当 if 语句中的命令返回退出状态码 0 时, then 部分中的命令会被执行,当 if 语句中的命令返回非零退出状态码时,bash shell会执行 else 部分中的命令,如下:
if command1; then
commands1
else
commands2
fi
嵌套 if
就像其他编程语言的 if 语句一样,bash shell也可以使用嵌套if,如下:
if command1; then
commands1
elif command2; then
commands2
elif command3; then
commands3
else
commands4
fi
其中,elif
是 else if
的简写。
test命令
有个不幸的消息,bash shell 的 if 语句后面只能判断命令退出的状态码,注意,是 只能
。
但有个好消息是,可以使用一些命令实现判断其他的条件,比如 test
命令,其格式如下:
test condition
其中 condition
是 test 命令要测试的一系列参数和值,即条件语句,当 condition
的值是 true
时,test condition
的退出状态码为 0
。
由于 a
是空的,所以 $a
条件语句的值是 false
, test $a
的退出状态码就是非零的,所以执行了 else
语句。
bash shell提供了另一种和 test
语句功能一样的条件测试方法——方括号 []
,格式是如下:
if [ condition ]
then
commands
fi
其中,第一个方括号之后和第二个方括号之前必须加上一个空格,否则就会报错。
他们可以判断下面三类条件:
- 数值比较
- 字符串比较
- 文件比较
接下来介绍怎么使用这些条件。
1. 数值比较
这里的数值比较不是用 > = <
等符号,而是有一套专用的符号,如下:
比较符号 | 描述 |
---|---|
-eq (equal) | 等于 |
-ge (greater or equal) | 大于等于 |
-gt (greater then) | 大于 |
-le (less or equal) | 小于等于 |
-lt (less than) | 小于 |
-ne (not equal) | 不等于 |
示例如下:
也可以使用方括号:
注意,方括号和所包含内容之间要用空格隔开。
另外,以上方法不支持浮点数的比较。
2. 字符串比较
以上方法还支持字符串比较,但比较符号不同
比较 | 描述 |
---|---|
str1 = str2 | str1 和 str2 相同 |
str1 != str2 | str1 和 str2 不相同 |
str1 \< str2 | str1 小于 str2 |
str1 \> str2 | str1 大于 str2 |
-n str1 | str1 的长度不为0 |
-z str1 | str1 的长度为0 |
注意小于和大于符号前面的 \
,它是转义符号,如果没有它bash shell会把 <
和 >
当成重定向符号。
其中,大于和小于的判断,是根据ASCII码的数值排序的。
示例如下:
-n 和 -z 可以检查一个变量是否为空,如下:
3. 文件比较
文件比较可以测试Linux文件系统上文件和目录的状态。
参数如下:
参数 | 描述 |
---|---|
-d object | 检查 object 是否存在并是一个目录 |
-e object | 检查 object 是否存在(文件和目录都使适用) |
-f object | 检查 object 是否存在并是一个文件 |
-r object | 检查 object 是否存在并可读 |
-s object | 检查 object 是否存在并非空 |
-w object | 检查 object 是否存在并可写 |
-x object | 检查 object 是否存在并可执行 |
-O object | 检查 object 是否存在并属当前用户所有 |
-G object | 检查 object 是否存在并且默认组与当前用户相同 |
object1 -nt object2 | 检查 object1 是否比 object2 新 |
object1 -ot object2 | 检查 object1 是否比 object2 旧 |
示例:
#!/bin/bash
if [ -d ./test ]; then
touch ./test/test.txt
else
mkdir ./test
touch ./test/test.txt
fi
检测当前目录下是否存在 test
目录,如果存在,在 test
目录里新建 test.txt
文件,如果不存在,新建 test
目录后,再在 test
目录里新建 test.txt
文件。其余使用方法同理。
bash shell 的 if-then 语句同样支持布尔逻辑,&&
和 ||
,如下:
#!/bin/bash
if [ -d ./test/test.txt ] && [ -w ./test/test.txt ]; then
echo "The file exists and you can write to it."
else
echo "cannot write to the file."
fi
test
命令只能操作简单的数学表达式,if-then
还有高级特性可以支持更丰富的功能。
- 用于数学表达式的双括号:
(( expression ))
- 用于高级字符串处理的双方括号:
[[ expression ]]
双括号除了支持 test
命令支持的标准数学运算外,还支持以下运算符:
符号 | 描述 |
---|---|
val++(++val) | 自增 |
val–(--val) | 自剪 |
! | 逻辑非 |
~ | 按位取反 |
** | 幂运算 |
<< | 按位左移 |
>> | 按位右移 |
& | 按位取与 |
| | 按位取或 |
&& | 逻辑与 |
|| | 逻辑或 |
可以在 if 语句中用双括号命令,也可以在脚本中的普通命令里使用来赋值,如下:
#!/bin/bash
val1=10
if (( $val1 ** 2 > 90 ))
then
(( val2 = $val1 ** 2 ))
echo "The square of $val1 is $val2"
fi
-----------------------------------------
The square of 10 is 100
双方括号除了支持 test
命令支持的标准字符串比较外,还支持 模式匹配
。
在模式匹配中,可以定义正则表达式来匹配字符串值,如下:
#!/bin/bash
if [[ $USER == v* ]]
then
echo "Hello $USER"
else
echo "Sorry, I do not know you"
fi
-----------------------------------------
Hello vistar
正则表达式在后面会介绍到。
3.2.2 分支语句
case 命令
case
命令采用列表格式来检查单个变量的多个值,如下:
#!/bin/bash
case $USER in
rich | barbara)
echo "Welcome, $USER"
echo "Please enjoy your visit";;
testing)
echo "Special testing account";;
jessica)
echo "Do not forget to log off when you're done";;
*)
echo "Sorry, you are not allowed here";;
esac
其中注意几点:
- case 语句以
case
开头,以esac
结尾。 - 在每个分支程序之后要以
;;
(双分号)结尾。 - 星号
*)
会捕获所有与已知模式不匹配的值。
3.2.3 循环语句
for 命令
for
命令的基本格式如下:
for var in list
do
commands
done
其中 var
是迭代变量, list
是需要迭代的值的列表(每个值用空格分隔,包含空格的值用双引号括起来),commands
是循环体,如下:
也可以将要迭代的值存在变量里,然后在for循环语句中引用这个变量:
麻烦的是,包含空格的值仍然需要放在for循环语句中。不过这个问题还是有办法解决的,就是更改内部字段分隔符,它通过环境变量 IFS
定义,默认情况下,字段分隔符为 空格
、制表符
、换行符
。如果想使用其他的符号作为字段分隔符,可以修改环境变量 IFS
的值,如下:
也可以指定多个字段分隔符,只需要在为IFS赋值时将多个符号串起来即可:
也可以使用命令的输出作为循环的迭代列表:
c语言风格的for循环语句
还记得支持复杂数学表达式的双括号码,双括号也可以使bash shell支持c语言风格的for循环语句。
while命令
while
命令会在每次迭代的一开始测试 test 命令。在 test 命令返回非零退出状态码时, while 命令会停止执行那组命令。
while 命令的格式如下:
while test command
do
other commands
done
其中, test command 和 if-then 语句中的格式一样。
while 命令允许在 while 语句行定义多个测试命令。但只有最后一个测试命令的退出状态码会被用来决定什么时候结束循环。注意,多个测试命令时,每个命令占一行,如下:
until命令
until
命令和 while
工作方式相反,只有测试命令的退出状态码不为 0 ,bash shell才会执行循环中列出的命令,一旦测试命令返回了退出状态码 0 ,循环就结束了,其他特性和 while
命令一样,不再赘述,其格式如下:
until test commands
do
other commands
done
break 命令
可以用 break
命令来退出任意类型的循环,包括for、while 和 until 循环。
break
命令有一个和其他编程语言不同的功能,就是它能 “远程控制” 别的循环,一般的编程语言的break只能跳出当前循环,但bash shell的 break
可以接收一个参数 n
,指定要停止的循环,n
默认为1,即当前循环。示例如下:
#!/bin/bash
for (( a = 1; a < 2; a++ ))
do
echo "3 loop: a=$a"
for (( b = 1; b < 4; b++ ))
do
echo " 2 loop: b=$b"
for (( c = 1; c < 100; c++ ))
do
if [ $c -gt 4 ]
then
break 2
fi
echo " 1 loop: c=$c"
done
echo " 2 loop: b=$b"
done
echo "3 loop: a=$a"
done
输出如下:
首先,外层循环 3
执行了1次,输出 a=1
,然后进入内层循环 2
,内层循环 2
执行1次,输出 b=1
,然后进入内层循环 1
,内层循环 1
判定如果 c>4
则执行 break 2
,该命令的意思是从当前层循环往外层开始数,连续跳出2层循环,事实也是这样,当 c=4
时又执行下一个循环 b=5
,此时满足判定条件,直接跳到了外层循环 3
第 19
行的命令,输出 a=1
,然后执行外层循环 3
的第 3
行命令 a++
,此时 a=2
,然后判定是否 a<2
,判定失败,结束外层循环 3
,程序结束。
continue命令
同 break
命令一样,continue
命令除了基本的停止本次循环继续下次循环的功能外,也可以使用参数停止外层循环。如下:
#!/bin/bash
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
代码第 8
行,当 a=3
时,执行 continue 2
,同 break 2
一样,2
代表从当前层循环往外层开始数的第2层循环,continue 2
的意思即停止外层循环 a=3
的这一次循环,继续执行 a=4
的循环。
处理循环的输出结果
可以对循环的输出使用管道或进行重定向,如下:
将循环的输出重定向到文件。
#!/bin/bash
for str in $(ls /home)
do
echo "Holle $str"
done > output.txt
也可以使用管道,将循环的输出结果传给其他命令,作其他处理。
#!/bin/bash
for str in $(ls /home)
do
echo "Holle $str"
done | grep vistar