详解结构化命令
-
- 使用if-then语句
-
- if-then-else语句
- 嵌套if语句
- elif语句
-
- test语句
-
-
- 数值比较
- 字符串比较
-
- 文件比较
-
- 检查目录
- 检查对象是否存在
- 检查文件
- 检查是否可读
- 检查非空文件
- 复合条件测试
- if-then高级特性
-
- case命令
- 在之前的两篇博客中举得例子,shell都是按照命令在脚本中自顶而下出现的顺序依次进行处理的,对于最常见的顺序操作来说,这已经足够了,但是并不是在所有情况下,都需要按照顺序执行,我们也需要使命令按照一定的顺序执行
- 至此,我们需要对shell脚本中的命令施加一些逻辑控制流程,其中有一类命令会根据条件使脚本跳过某些命令。这样的命令通常称为结构化命令(structured command),结构化命令允许你改变程序的执行顺序,如:if-then语句、case语句,下面具体会介绍
使用if-then语句
- 最基础的结构化命令就是if-then语句,命令格式如下:
if command
then
commands
fi
- 如果在学习shell脚本之前你已经有别的编程语言基础了,那么shell脚本的if-then命令多少会使你感到困惑,在别的语言中,if后面跟得一般都是一个表达式(等式),这个表达式的求值结果一般为TRUE或FALSE。但是bash shell的if-then语句确实非常不同的
- bash shell的if语句会运行if后面的那个命令,如果该命令的退出状态码为0(该命令成功执行),位于then部分的命令就会被执行。如果该命令的的退出状态码是其他值,then部门的命令是不会执行的,bash shell会继续向下执行其它命令,fi语句用来表示if-then语句到此结束,如下先举个简单例子:
if pwd
then
echo "pwd成功执行!!"
fi
- 上述这个简单的脚本,if的判断条件是pwd命令是否成功执行,pwd成功执行后,echo语句就会显示上述的那串文本
- 下面是if判断语句没有成功执行的例子,如下:
if wy
then
echo "wy 命令成功执行!!"
fi
echo "if 语句执行完后执行此语句!"
- 在上述的例子中,我们在if语句中故意放了一个并不能成功执行的命令,由于此命令是个错误命令,并不能成功执行且会返回一个非零的退出状态码,所以bash shell会跳过then部分的echo语句,还得注意,运行if语句中的那条错误命令所生成的错误信息依然会显示到脚本的输出中(不论命令执行成功还是失败,都会将命令执行的返回信息显示到脚本的输出中,如上述正确的pwd与错误的wy),如果我们并不想看到if后面的命令输出信息,后续会进行介绍的
- 而且我们还可以在then部分,使用不止一条命令。可以在像在脚本的其他地方一样列出多条命令,bash shell会将这些在then部分的命令当成一整个块,如果if语句行的命令的退出状态码值为0,所有在then部分的命令都会被执行;反之,命令都不执行
注意:
- 你可能在有些别的脚本中看到过另一种if-then格式,如下:
if command; then
commands
fi
- 这种格式通常把分号(;)放在带判断命令的尾部,就可以将then命令放在同一行中,这样看起来与其他编程语言中的if-then语句一样,功能实现与上述的另一种格式并无区别!!
if-then-else语句
- 在上述的if-then语句中,不管命令是否成功执行,你都只有一种选择,如果命令返回一个非零的退出状态码,bash shell会继续执行脚本的下一条命令,在这种情况下,如果能够执行另一组命令,岂不美哉!!这就是if-then-else语句中else的作用,命令格式如下:
if command
then
commands
else
commands
fi
- 上述的格式中,当if语句中的命令返回状态码值为0时,then部分中的命令会被执行,与上述的if-then语句相同,不同的是,当if语句中的命令返回状态码值为非0时,bash shell会执行else部分中的命令,如下:
testuser=Nouser
if grep $testuser /etc/passwd
then
echo "$testuser用户存在!!"
else
echo "$testuser用户不存在!"
fi
- 这种格式是更加方便的,跟then部分一样,else部分也可以包含多条命令,fi语句说明else部分结束了
嵌套if语句
- 在某些情况我们需要检查脚本代码,对此,可以使用嵌套的if-then语句,嵌套的if-then语句位于主if-then-else语句的else代码块中,如下:
testuser=wy
if grep $testuser /etc/passwd
then
echo "系统存在$testuser用户"
else
echo "系统不存在$testuser用户"
if ls -d /home/$testuser/
then
echo "但是,系统存在$testuser的家目录"
fi
fi
- 上述脚本准确发现wy用户不存在于/etc/passwd,但是该用户的家目录还是存在的,但是在脚本中使用使用这种嵌套if-then语句,使得代码不易阅读且很难理清逻辑流程
elif语句
- 可以使用elif语句,来解决上述问题,elif语句像是使用了另一个if-then语句延续else部分,命令格式如下:
if command1
then
commands
elif command2
then
more commands
fi
- elif语句提供另一个要进行运行测试的命令,类似于if语句行,如果elif语句后命令的退出状态码的值为0,则bash shell会执行第二个then语句(属于elif的then)部分的命令,使用这种不同于上述的嵌套方法,使得代码易读,逻辑清晰,如下:
testuser=wy
if grep $testuser /etc/passwd
then
echo "系统存在$testuser用户"
elif ls -d /home/$testuser/
then
echo "系统不存在$testuser用户"
echo "但是,系统存在$testuser的家目录"
fi
- 我们甚至可以更进一步,让脚本检查拥有目录的不存在用户以及没有拥有目录的不存在用户。可以通过在嵌套elif中加入一个else语句来实现,如下:
testuser=wyy
if grep $testuser /etc/passwd
then
echo "系统存在$testuser用户"
elif ls -d /home/$testuser/
then
echo "系统不存在$testuser用户"
echo "但是,系统存在$testuser的家目录"
else
echo "系统不存在$testuser用户"
echo "系统不存在$testuser的家目录"
fi
- 上述例子,先检查当前系统使用存在$testuser这个用户,当返回状态码的值不为0后(即为不存在),执行elif后的语句,状态值也不为0,最后执行else中的语句
注意:
- 在elif语句中,紧跟其后的else语句属于elif代码块。它们并不属于之前的if-then代码块
- 在elif语句中,紧跟其后的else语句属于elif代码块。它们并不属于之前的if-then代码块
- 在elif语句中,紧跟其后的else语句属于elif代码块。它们并不属于之前的if-then代码块
还可以继续将多个elif语句串联在一起,形成一个大的if-then-elif…嵌套组合,如下格式:
if command1
then
commands
elif command2
then
commands
elif command3
then
commands
elif command4
then
commands
fi
- 每块命令都会根据命令是否会返回退出状态码0来执行
- 注意:bash shell会依次执行if语句与elif语句,只有第一个返回退出状态码0的语句中的then部分会被执行
- 尽管使用了elif语句的代码看起来更清晰,但是脚本的逻辑仍然会让人犯晕,下面我们会学习如何使用case命令来代替包含if-then-elif…嵌套的语句
test语句
- 到目前为止,在上述if语句中看到的都是普通shell命令。你可能想问,if-then语句是否能测试命令退出状态码之外的条件,答案是不能。但在bash shell中有个好用的工具可以帮你通过if-then语句测试其他条件
- test命令提供了在if-then语句中测试不同条件的途径。如果test命令中列出的条件成立,test命令就会退出并返回退出状态码0。这样if-then语句就与其他编程语言中的if-then语句以类似的方式工作了。如果条件不成立,test命令就会退出并返回非零的退出状态码,这使得if-then语句不会再被执行,命令格式如下:
test condition
- condition是test命令要测试的一系列参数和值。当用在if-then语句中时,test命令看起来是这样的,如下:
if test condition
then
command
fi
- 如果不写test命令的condition部分,会以非零的退出状态码退出,并执行else语句块
- 当你加入一个条件时,test命令会测试该条件。例如,可以使用test命令确定变量中是否有值,如下:
var1='wy'
if test $var1
then
echo "var1的值为$var1!!"
else
echo "var1不存在值!!"
fi
- 上述例子中,变量var1中包含有内容(wy),因此当test命令测试条件时,返回的退出状态为0。这使得then语句块中的语句得以执行,如果该变量中没有包含内容,就会出现相反的情况,执行else语句块的语句
注意:
- bash shell还提供了另一种条件测试方法,无需在if-then语句中声明test命令,如下格式
if [ condition ]
then
commands
fi
- 方括号定义了测试条件。注意,第一个方括号之后和第二个方括号之前必须加上一个空格,否则就会报错
- test命令可以判断三类条件:
数值比较
- 使用test命令最常见的情况就是对两个数值进行比较,如下表,列出了测试两个值是可用的条件参数
比 较 |
详细描述 |
n1 -eq n2 |
检查n1是否与n2相等 |
n1 -ge n2 |
检查n1是否大于或等于n2 |
n1 -gt n2 |
检查n1是否大于n2 |
n1 -le n2 |
检查n1是否小于或等于n2 |
n1 -lt n2 |
检查n1是否小于n2 |
n1 -ne n2 |
检查n1是否不等于n2 |
var1=10
var2=12
if test $var1 -gt 5
then
echo "$var1大于5!!"
else
echo "$var1小于5!!"
fi
if [ $var1 -eq $var2 ]
then
echo "$var1等于$var2!!"
else
echo "$var1不等于$var2!!"
fi
运行结果如下:
- 但是涉及浮点值时,数值条件测试会有一个限制,如下:
var1=666.666
echo "var1的值为$var1!!"
if test $var1 -gt 5
then
echo "$var1大于5!!"
fi
运行结果:
- 记住,bash shell只能处理整数。如果你只是要通过echo语句来显示这个结果,那没问题,但是,在基于数字的函数中就不行了,如我们的数值测试条件。最后一行就说明我们不能在test命令中使用浮点值
字符串比较
- 条件测试还可以比较字符串值。字符串比较是比较烦琐的,下表列出了可用的字符串比较功能
比 较 |
详细描述 |
str1 = str2 |
检查str1是否和str2相同 |
str1 != str2 |
检查str1是否和str2不同 |
str1 < str2 |
检查str1是否比str2小 |
str1 > str2 |
检查str1是否比str2大 |
-n str1 |
检查str1的长度是否非0 |
-z str1 |
检查str1的长度是否为0 |
字符串相等性
- 就和它字面意思一样,就是判断两个字符串的值是否相同,如下:
testuser=root
if [ $USER=$testuser ]
then
echo "Welcom $testuser!!"
fi
- 注意:在比较字符串的相等性时,比较测试会将所有的标点和大小写情况都考虑在内!!
字符串顺序
- 要测试一个字符串是否比另一个字符串大就是麻烦的开始。当要开始使用测试条件的大于或小于功能时,就会出现两个棘手的问题:
- 大于号和小于号必须转义,否则shell会把它们当作重定向符号,把要比较的字符串值当作文件名
- 大于和小于顺序和sort命令所采用的不同
- 首先展示第一个不易察觉且又严重问题,如下:
var1=wy
var2=fl
if [ $var1 > $var2 ]
then
echo "$var1大于$var2!!"
else
echo "$var1小于$var2!!"
fi
运行结果:
- 这个脚本中只用了大于号,没有出现错误,但结果是错的。脚本把大于号解释成了输出重定向。因此,它创建了一个名为fl的文件。由于重定向的顺利完成,test命令返回了退出状态码0,if语句便以为所有命令都成功结束
- 解决方案:只需要正确的转义大于号(>),小于号也是一样操作,如下:
var1=wy
var2=fl
if [ $var1 \> $var2 ]
then
echo "$var1大于$var2!!"
else
echo "$var1小于$var2!!"
fi
运行结果:
- 第二个问题更细微,除非你需要在脚本中经常处理大小写字母,否则几乎遇不到。sort命令处理大写字母的方法刚好跟test命令相反,如下:
var1=Wy
var2=wy
if [ $var1 \> $var2 ]
then
echo "$var1大于$var2!!"
else
echo "$var1小于$var2!!"
fi
运行结果:
- 将两个字符串保存到一个文件并使用sort命令就行排序,如下:
- 在脚本的比较测试中,大写字母被认为是小于小写字母的。但sort命令恰好相反。当你将同样的字符串放进文件中并用sort命令排序时,小写字母会先出现。这是由各个命令使用的排序方法不同造成的
- 比较测试中使用的是标准的ASCII顺序,根据每个字符的ASCII数值来决定排序结果。sort命令使用的是系统的本地化语言设置中定义的排序顺序。对于英语,本地化设置指定了在排序顺序中小写字母出现在大写字母前
- 注意:test命令和测试表达式使用标准的数学比较符号来表示字符串比较,而用文本代码来表示数值比较。需要注意这个细微的特性。如果你对数值使用了数学运算符号,shell会将它们当成字符串值,可能无法得到正确的结果,简单来说就是:字符串大小比较用 > 、<等等,数值比较大小用-eq、-gt等等
字符串大小
var1=wy
var2=''
if [ -n $var1 ]
then
echo "'$var1'不为空!!"
else
echo "'$var1'为空!!"
fi
if [ -z $var2 ]
then
echo "'$var2'为空!!"
else
echo "'$var2'不为空!!"
fi
if [ -z $var3 ]
then
echo "'$var3'为空!!"
else
echo "'$var3'不为空!!"
fi
运行结果:
- 上述创建了两个字符串变量。var1变量包含了一个字符串,var2变量包含的是一个空
字符串。比较如下:
- if [ -n $var1 ]
- 判断var1变量是否长度非0,而它的长度正好非0,所以then部分被执行了
- if [ -z $var2 ]
- 判断val2变量是否长度为0,而它正好长度为0,所以then部分被执行了
- if [ -z $val3 ]
- 判断val3变量是否长度为0。这个变量并未在shell脚本中定义过,所以它的字符串长度仍然为0,尽管它未被定义过
- 注意:空的和未初始化的变量会对shell脚本测试造成灾难性的影响。如果不是很确定一个变量的内容,最好在将其用于数值或字符串比较之前先通过-n或-z来测试一下变量是否含有值,在进行后续操作
文件比较
- 文件比较测试是shell编程中最为强大、也是用得最多的比较形式。允许你测试Linux文件系统上文件和目录的状态,如下表列出这些比较:
比 较 |
详细描述 |
-d file |
检查file是否存在并是一个目录 |
-e file |
检查file是否存在 |
-f file |
检查file是否存在并是一个文件 |
-r file |
检查file是否存在并可读 |
-s file |
检查file是否存在并非空 |
-w file |
检查file是否存在并可写 |
-x file |
检查file是否存在并可执行 |
-O file |
检查file是否存在并属当前用户所有 |
-G file |
检查file是否存在并且默认组与当前用户相同 |
file1 -nt file2 |
检查file1是否比file2新 |
file1 -ot file2 |
检查file1是否比file2旧 |
- 上述表中的测试条件使你能够在shell脚本中检查文件系统中的文件。经常出现在需要进行文件访问的脚本中。下面进行详细介绍:
检查目录
- -d测试会检查指定的目录是否存在于系统中。如果你打算将文件写入目录或是准备切换到某个目录中,建议先进行这个测试,如下:
jump_dir=/home/wy
if [ -d $jump_dir ]
then
echo "$jump_dir此目录存在!!"
cd $jump_dir
ls
else
echo "$jump_dir此目录不存在!!"
fi
运行结果:
- 上述例子使用了-d测试条件来检查jump_dir变量中的目录是否存在:若存在,就
使用cd命令切换到该目录并列出目录中的内容;若不存在,脚本就输出一条警告信息,然后退出
检查对象是否存在
- -e比较允许脚本代码在使用文件或目录前先检查它们是否存在,如下:
location=$HOME
file_name="now_date"
if [ -e $location ]
then
echo "$location目录存在!!"
echo "然后检查$location目录下的$file_name是否存在?"
if [ -e $location/$file_name ]
then
echo "$file_name文件也存在!!"
echo "更新当前时间!!"
date >> $location/$file_name
else
echo "$file_name文件不存在!!"
fi
else
echo "$location目录不存在!!"
fi
运行结果:
- 如上:第一次检查用-e比较来判断用户是否$HOME目录。如果有,接下来的-e比较会检查now_date文件是否存在于$HOME目录中。如果不存在,shell脚本就会提示该文件不存在,不需要进行更新
- 为确保更新操作能够正常进行,我们创建了now_date文件,然后运行这个shell脚本。这次在进行条件测试时,$HOME和now_date文件都存在,因此当前日期和时间就被追加到了文件中
检查文件
- 要确定指定对象为文件,可用**-f比较**,如下:
item_name=$HOME/shell-23-04-06/now_date
if [ -e $item_name ]
then
echo "$item_name存在!!"
echo "但是它是文件吗?"
if [ -f $item_name ]
then
echo "$item_name是一个文件!!"
else
echo "$item_name不是一个文件!!"
fi
else
echo "$item_name不存在!!"
fi
运行结果如下:
- 上述脚本进行了大量的检查!它首先使用-e比较测试$HOME/shell-23-04-06/now_date是否存在。如果存在,继续用-f来测试它是不是一个文件。如果它是文件,就会显示一条消息,表明这是一个文件
检查是否可读
- 在尝试从文件中读取数据之前,建议先测试一下文件是否可读。可以使用-r比较测试,如下:
先查看我们要测试的文件是否可读,即为不可读:
脚本测试:
item=$HOME/shell-23-04-06/now_date
if [ -f $item ]
then
if [ -r $item ]
then
cat $item
else
echo "$item此文件不可读!!"
fi
else
echo "$item此文件不存在!!"
fi
检查非空文件
- 我们在脚本中删除文件时,可以使用-s比较来检查文件是否为空,尤其是在不想删除非空文件的时候。要注意的是,当-s比较成功时,说明文件中有数据,如下:
先查看我们要测试的文件是否为空,即为有值:
脚本测试:
item=$HOME/shell-23-04-06/now_date
if [ -f $item ]
then
if [ -s $item ]
then
echo "$item此文件存在且不为空!!"
echo "不可以删除非空文件!!"
else
echo "$item此文件存在且为空!!"
echo "正在删除此文件!!"
rm $item
fi
else
echo "$item此文件不存在!!"
fi
运行结果如下:
- -f比较测试首先测试文件是否存在。如果存在,由-s比较来判断该文件是否为空。空文件会被删除。可以从ls –l的输出中看出now_date并不是空文件,因此脚本并不会删除它
复合条件测试
- if-then语句允许你使用布尔逻辑来组合测试。有两种布尔运算符可用:
- [ condition1 ] && [ condition2 ](且、AND)
- [ condition1 ] || [ condition2 ](或、OR)
- 第一种布尔运算使用AND布尔运算符来组合两个条件。要让then部分的命令执行,两个条件都必须满足
- 第二种布尔运算使用OR布尔运算符来组合两个条件。其中任意一个条件为TRUE,then部分的命令就会执行
if-then高级特性
- bash shell提供了两项可在if-then语句中使用的高级特性:
- 用于数学表达式的双括号
- 用于高级字符串处理功能的双方括号
使用双括号
- 双括号命令允许你在比较过程中使用高级数学表达式。test命令只能在比较中使用简单的算术操作。双括号命令提供了更多的数学符号,命令格式如下:
(( expression ))
- expression可以是任意的数学赋值或比较表达式。除了test命令使用的标准数学运算符,还可以为下表中的运算符:
表 格 |
详细描述 |
val++ |
后增 |
val– |
后减 |
++val |
先增 |
–val |
先减 |
! |
逻辑求反 |
~ |
位求反 |
** |
幂运算 |
<< |
左位移 |
>> |
右位移 |
& |
位布尔和 |
| |
位布尔或 |
&& |
逻辑和 |
|| |
逻辑或 |
- 可以在if语句中用双括号命令,也可以在脚本中的普通命令里使用来赋值,如下:
var1=10
if (( var1 ** 2 > 90 ))
then
(( var2 = $var1 ** 2 ))
echo "$var1的平方为$var2!!"
fi
运行结果:
- 注意,我们并不需要将双括号中表达式里的大于号转义。因为这是双括号命令提供的另一个高级特性
使用双方括号
- 双方括号命令提供了针对字符串比较的高级特性。双方括号命令的格式如下:
[[ expression ]]
- 双方括号里的expression使用了test命令中采用的标准字符串比较。但它提供了test命令未提供的另一个特性——正则模式匹配(pattern matching),但是要注意,不是所有的shell都支持双方括号,如下:
if [[ $USER == r* ]]
then
echo "你好,$USER"
else
echo "你是????"
fi
运行结果如下:
- 在上述脚本中,我们使用了双等号(==)。双等号将右边的字符串(r*)视为一个模式,并应用模式匹配规则。双方括号命令$USER环境变量进行匹配,看它是否以字母r开头。如果是的话,比较通过,shell会执行then部分的命令
case命令
- 有时我们会发现自己在尝试计算一个变量的值,在一组可能的值中寻找特定值。在这种情形下,不得不写出很长的if-then-else语句,但当我们使用case命令后,就不需要再写出所有的elif语句来不停地检查同一个变量的值了。case命令会采用列表格式来检查单个变量的多个值,命令格式如下:
case variable in
pattern1 | pattern2) commands1;;
pattern3) commands2;;
*) default commands;;
esac
- case命令会将指定的变量与不同模式进行比较。如果变量和模式是匹配的,那么shell会执行为该模式指定的命令。可以通过竖线操作符在一行中分隔出多个模式模式。星号会捕获所有与已知模式不匹配的值,如下:
case $USER in
wy | root)
echo "欢迎您,$USER VVIP用户";;
test )
echo "欢迎您,$USER VIP用户";;
*)
echo "您是哪一位??";;
esac
运行结果如下:
- 上述的case命令提供了一个更为清晰的方法,来为变量每个可能的值指定不同的选项!!!