结构化命令允许我们改变程序执行的顺序,在某些条件下执行一些命令而在其他条件下跳过另一些命令。
(1)使用if-then语句
结构化命令中,最基本的类型就是if-then语句,其格式如下:
if command
then
commands
fi
bash shell 的if语句会运行if行定义的那些命令。如果该命令的退出状态码是0(该命令成功运行),位于then部分的命令就会被执行;否则,then部分的命令就不会被执行,bash shell会继续执行脚本中的下一条命令。#cat test1 #!/bin/bash #testing if statement if date then echo "is a good day!" fi
运行结果是:
./test1 Sun May 14 18:39:55 CST 2017 is a good day!
#cat test1 #!/bin/bash # testing a bad command if abdca then echo "is a good day!" fi echo "outside of the if statement"
运行结果是:
#./test1 ./test1: line 3: abdca: command not found outside of the if statement
- 在这个例子中,我们在if语句中放了一个不能工作的命令。由于这个命令不能正常运行,所以其返回一个非0的退出状态码,bash shell 会跳过then部分的echo语句。注意,运行if语句中的那个命令所生成的错误信息仍然会显示在脚本的输出中。后续讲解如何避免这种情况的发生。
then部分可以有多个命令。bash shell 会将这部分命令当成一个块,在if语句行中的命令返回退出状态码为0时执行这块命令,否则跳过这块命令:
cat test1 #!/bin/bash # testing mutiple commands in the then section testuser=xiaoyu1 if grep $testuser /etc/passwd then echo The bash files for user $testuser are: ls -a /home/$testuser/shell fi
运行结果是:
#./test1 xiaoyu1:x:1001:1001::/home/xiaoyu1:/bin/bash The bash files for user xiaoyu1 are: . .. date_who pathfile test1 test2 test3 test4 test6 test7
(2)if-then-else语句
格式为:
if command
then
commands
else
commands
fi当if语句中的命令返回的退出状态码为0时,then部分中的命令会被执行;当if语句中的命令返回的退出状态码非0时,bash shell 会执行else部分中的命令。
#cat test2
#!/bin/bash
# testing mutiple commands in the then section
testuser=badtest
if grep $testuser /etc/passwd
then
echo The bash files for user $testuser are:
ls -a /home/$testuser/shell
else
echo "The user name $testuser does not on this system"
fi
执行结果是:
#./test2
The user name badtest does not on this system
(3)嵌套if
有时需要检查脚本代码中的多个条件。不需要写多个分立的if-then语句,可以用else部分的替代版本,elif。
elif会通过另一个if-then语句来延续else部分:if command1 then commands elif command2 then more commands fi
elif语句行提供了另一个需要测试的命令,类似于原始的if语句。如果elif后的命令的退出状态码是0,则bash会执行第二个then语句部分的命令。
我们可以继续将多个elif语句串联起来,形成一个大的if-then-elif嵌套组合:
if command1 then command set 1 elif command2 then command set 2 elif command3 then command set 3 elif command4 then command set 3 fi
每块命令都会根据哪个命令会返回退出状态0来执行。注意,bash shell会以此执行if语句,只有第一个返回状态码0的语句中的then部分会被执行(此时,被执行的then之后的所有语句都不会被执行)。后续将介绍如何使用case命令替换嵌套多个if-then语句。
(4)test命令
- test命令提供了在if-then语句中测试不同条件的途径(跟命令的退出状态码无关的条件)。如果test命令中列出的条件成立,test命令就会退出并返回退出状态码0,接着就会执行then语句;否则,test命令退出并返回退出状态码1,then语句就会被跳过。
test命令的格式为:test condition
- condition是test命令要测试的一些列参数和值。当用在if-then语句中时,test命令格式如下:
if test condition then commands fi
bash shell 提供了另一种在if-then语句中声明test命令的方法:
if [ condition ] then commands fi
方括号定义了test命令中用到的条件。注意,必须在左括号的右边和右括号的左边各加一个空格,if 和左括号之间也必须有空格;否则会报错。
- test 命令可以判断3类条件:
- 数值比较;
- 字符串比较;
- 文件比较。
test用于数值比较:
- 下表列出了测试两个值时可用的条件参数:
- 数值条件测试可用于数字和变量上。例如:
$cat test8 #!/bin/bash # using numeric test comparisons var1=10 var2=11 if [ $var1 -gt 5 ] then echo "The test value $var1 is greater than 5" fi if [ $var1 -eq $var2 ] then echo "The value are equal" else echo "The value are difference" fi
运行结果如下:
$./test8 The test value 10 is greater than 5 The value are difference
- 下表列出了测试两个值时可用的条件参数:
但在测试数值条件时有一个限制。例如:
$cat test9 #!/bin/bash # testing floating point numbers var1=`echo "scale=4; 10 / 3" | bc` echo "The test value is $var1" if [ $var1 -gt 3 ] then echo "The result is larger than 3" fi
运行结果是:
$./test9 The test value is 3.3333 ./test9: line 5: [: 3.3333: integer expression expected
- 这个例子是使用了bash计算器来生成一个浮点数并存储在变量 var1 中。下一步,使用了 test 命令来判断这个值。这里显然出错了。
- 最后一句说明,test 命令无法处理var1 变量中存储的浮点值。
- 注意,bash shell 能处理的数仅有整数。当使用 bc时,我们可以让shell将浮点值作为字符串值存储进一个变量。如果你只要通过echo显示这个结果,那它可以很好的工作;但它无法再基于数字的函数中工作,例如我们的数值测试条件。尾行恰好说明,不能再test中使用浮点值。
test用于字符串比较:
test命令还可用于对字符串的比较。但对字符串的比较很繁琐,下表列出了可用来比较两个字符串值的函数。
接下来会详细说明不同的字符串比较功能:字符串相等性:
字符串的相等和不等的条件是,两个字符串的值是否相同:$cat test10 #!/bin/bash # testing string equality testuser=root if [ $USER = $testuser ] then echo "welcome $testuser" fi
运行结果是:
$./test10 welcome root
比较字符串的相等性时,test 的比较会将所有的标点和大写也考虑在内。
字符串的顺序:
大于小于符号必须转义(在前面添加),否则shell会把它们当做重定向符号而把字符串值当做文件名;
#!/bin/bash var1=baseball var2=hockey if [ $var1 \> $var2 ] then echo "$var1 is greter than $var2" else echo "$var1 is less than $var2" fi
大于小于顺序和sort命令所采用的排序方式不同;
#!/bin/bash var1=Testing var2=testing if [ $var1 \> $var2 ] then echo "$var1 is greter than $var2" else echo "$var1 is less than $var2" fi
在test命令根据ASCII 数值排序;sort命令使用系统的本地化语言设置中定义的顺序排序。对于英语,本地化设置指定了在排序顺序中小写字母出现在大写字母的前面。
字符串大小:
- -n 和 -z 参数用来检查一个变量是否为空:
#!/bin/bash val1=testing val2='' if [ -n "$val1" ] then echo "The string '$val1' is not empty" else echo "The string '$val1' is empty" fi if [ -z "$val2" ] then echo "The string '$val2' is not empty" else echo "The string '$val2' is empty" fi if [ -z "$val3" ] then echo "The string '$val3' is not empty" else echo "The string '$val3' is empty" fi
- -n 和 -z 参数用来检查一个变量是否为空:
注意:空的和未初始化的变量对shell脚本测试来说可能是灾难性的影响。如果不确定一个变量的内容,最好在数值或字符串比较中使用它之前先通过 -n 或 -z 来测试一下变量是否含有值。
文件比较:
最后一类测试比较可能是shell编程中最强大且使用最多的比较。test 命令允许你测试Linux文件系统上文件和目录的状态。如下表所示:
这些条件能够使你在shell脚本中检查文件系统中的文件,并且常用在访问文件的脚本中。检查目录:
$cat test111 #!/bin/bash # look before you leap if [ -d $HOME ] then echo "Your HOME directory exists" cd $HOME ls -a else echo "There is a problem with your HOME directory" fi
检查对象是否存在
-e 比较允许你在脚本使用对象前检查文件或目录对象是否存在:#!/bin/bash # checking if a directory exists if [ -e $HOME ] then echo "OK on the directory. now to check the file" # checking if a file exists. if [ -e $HOME/testing ] then # the file exists. append data to it echo "Appending date to existing file" date >> $HOME/testing else # the file does not exist.create a new file echo "Creating new file" date > $HOME/testing fi else echo "you do not have a HOME directory" fi
检查文件
-e 比较适合用于文件和目录。要确定指定的对象是个文件,必须用 -f 比较:- 检查是否可读
在尝试从文件中读取数据之前,最好先测试一下是否能读取文件。可以使用 -r 测试: - 检查空文件
在删除文件时,你首先应该使用 -s 比较来检测文件是否为空。当 -s 比较成功时要特别小心,它说明文件中有数据: - 检查是否可写
-w 比较会判断你是否对文件有可写权限: - 检查是否可执行
-x 比较可以判断你对某个特定文件是否有执行权限。如果你要在shell脚本中运行大量脚本,它很方便: - 检查所属关系
-O 比较可以测试你是否是文件的拥有者(ownership): 检查默认属组关系
-G 比较会检查文件的默认组(主组),如果它匹配了用户的默认组,就能通过。由于 -G 比较只会检查默认组而非用户所属的所有组,所以需要注意。
#!/bin/bash if [ -G $HOME/shell/t19test ] then echo "You are in the same group as the file" else echo "The file is not owned by your group" fi
$ls -l t19test -rw-rwxr-- 1 xiaoyu1 xiaoyu1 0 May 16 14:37 t19test
[xiaoyu1@iZ23aoz1vd9Z ~]$./test19 You are in the same group as the file
#cat /etc/group ... xiaoyu1:x:1001: hadoop:x:1002:xiaoyu1
$chgrp hadoop t19test
[xiaoyu1@iZ23aoz1vd9Z shell]$./test19 The file is not owned by your group
注意:第一次运行脚本时,$HOME/t19test 文件是在xiaoyu1组里,所以-G通过了。下一次,文件的组被改成了hadoop组,虽然xiaoyu1用户也是hadoop组里的成员(运行程序可以看出),但-G比较失败了,因为它只比较默认组(主组),不会去比较其他额外组(附属组)。
检查文件日期
- 用来比较两个文件创建日期。这在编写安装软件的脚本时非常有用(判断要安装的软件版本是否比系统上已安装的新)。
- -nt 比较判断某个文件是否比另一个文件更新。如果文件更新,那它会有较近的创建日期。
- -ot 比较判断某个文件是否比另一个文件更老。如果文件更老,它会有一个更早的创建日期。
- 注意:比较用到的文件路径是相对于你所运行的脚本的目录来说的。如果要检查的文件可以移动,这可能会造成问题。
注意,另一个问题是这些比较中没有哪个会先检查文件是否存在。
#!/bin/bash # testing file dates if [ ./badfile1 -nt ./badfile2 ] then echo "The badfile1 is newer than badfile2" else echo "The badfile1 is old than badfile2" fi
运行结果
./test21 The badfile1 is old than badfile2
- 这个例子说明,如果文件不存在,-nt 比较会返回一个无效的条件(非0的退出状态码)。所以,在尝试使用-nt 或 -ot 之前,必须先确认文件存在。
(5)复合条件测试
if-then 语句允许使用布尔逻辑来组合测试。有两种布尔运算符可用:
- [ condition1 ] && [ condition2 ] (AND,两个条件都满足,才会执行then部分命令)
- [ condition1 ] || [ condition2 ] (OR,任意一个条件满足,即可执行then部分命令)
(6)if-then的高级特性
使用双尖括号
双尖括号命令允许我们将高级数学表达式放入比较中。test 命令只允许在比较中进行简单的算术操作。双尖括号命令提供了更多编程人员所熟悉的数学符号。尖括号的格式为:
(( expression ))- 术语 expression 可以是任意的数学赋值或比较表达式。除了test命令使用的标准数学运算符,下表给出了双尖括号命令中会用到的其他运算符。
- 你可以在if语句中使用双尖括号,也可以在脚本中的普通命令里使用来赋值:
#!/bin/bash val1=10 if (( $val1 ** 2 > 90 )) then (( val2 = $val1 ** 2 )) echo "The square of $val1 is $val2" fi
- 注意,双尖括号中的表达式里的大于号不需要转义。
- 术语 expression 可以是任意的数学赋值或比较表达式。除了test命令使用的标准数学运算符,下表给出了双尖括号命令中会用到的其他运算符。
使用双方括号
- 双方括号命令提供了针对字符串比较的高级特性。命令格式如下: [[ expression ]]
双方括号里的 expression 使用了 test 命令中所采用的标准字符串进行比较。但它还提供了test命令未提供的另一特性,模式匹配(pattern matching)。
在模式匹配中,你可以定义一个正则表达式来匹配字符串值:
#!/bin/bash if [[ $USER == xiao* ]] then echo "Hello $USER" else echo "sorry." fi
双方括号命令匹配了$USER环境变量来看它是否以字符串xiao开头。如果是,比较通过,shell会执行then部分的命令。
(7)case命令
我们经常会尝试计算一个变量的值或在一组可能值中寻找特定值。在这种情景下,需要写出很长的if-then-else 语句,像这样:
#!/bin/bash if [ $USER = "root" ] then echo "Welcome $USER" elif [ $USER = "xiaoyu" ] then echo "Welcome $USER" elif [ $USER = "testing" ] then echo "Special testing account" elif [ $USER = "user1" ] then echo "Do not foget to logout when you're done" else echo "Sorry.you are not allowed here" fi
可以使用case命令改写上面脚本。case命令会检查单个变量列表格式的多个值:
case variable in
pattern1 | pattern2) commands1;;
pattern3) command2;;
*) default commands;;
esaccase 命令会将制定的变量同不同模式进行比较。如果变量和模式匹配,shell会执行为该模式指定的命令。可以通过|操作符来分割模式,在一行列出多个模式。*会捕获所有跟所有列出的模式都不匹配的值。例如:
#!/bin/bash case $USER in root | xiaoyu1) echo "Welcome $USER" echo "Please enjoy your visit";; testing) echo "Special testing account";; user1) echo "Do not foget to logout when you're done";; *) echo "Sorry.you are not allowed here";; esac
(8)小结
- 结构化命令允许你改变shell脚本的正常执行流。