6.1 条件测试
首先介绍test命令,并分别介绍文件属性的测试、字符串测试和算术测试,然后介绍if语句的各种结构,逻辑与、或、非,最后介绍case语句。
6.1.1 实例:使用test命令
Shell脚本可以使用条件逻辑,使脚本可以根据参数、Shell变量或是其它条件的值采取不同的行动。test命令允许你做各种测试并每当测试成功或失败时设置它的退出状态码为0(表示真)或1(表示假)。使用这个退出状态码,可以让Bash对测试的结果做出反应。
test命令可以用于:
- 文件属性测试
- 字符串测试
- 算术测试
test命令的语法如下所示:
test EXPRESSION 或 [ EXPRESSION ]
使用test命令的几个实例:
[root@Master ~]# test -d "$HOME"; echo $? # 文件属性的测试,使用-d操作符测试变量$HOME的值是否为一个目录并且此目录是否存在 0 [root@Master ~]# [ "abc" != "def" ]; echo $? # 两个字符串不相等,退出的状态码为0,表示真。 0 [root@Master ~]# test 7 -gt 3 && echo True || echo False # 7大于3打印True True
6.1.1.1 文件属性测试的操作符,参考下表:
操 作 符 | 描 述 |
-e | 如果存在则为真 |
-f | 如果存在且是一个常规文件则为真 |
-d | 如果存在且是一个目录则为真 |
-c | 如果存在且是一个特殊字符文件则为真 |
-b | 如果存在且是一个特殊块文件则为真 |
-p | 如果存在且是一个命名管道则为真 |
-S | 如果存在且是一个套接字文件则为真 |
-L | 如果存在且是一个符号链接则为真(与 -h 相同) |
-h | 如果存在且是一个符号链接则为真(与 -L 相同) |
-g | 如果存在且是设置了sgid位则为真 |
-u | 如果存在且是设置了suid位则为真 |
-r | 如果存在且是可读的则为真 |
-w | 如果存在且是可写的则为真 |
-x | 如果存在且是可执行的则为真 |
-s | 如果存在且不为空则为真 |
-t | 如果文件描述符已打开且引用了一个终端则为真(文件描述符将在11.3节中介绍) |
-nt | 如果比新则为真(指 mtime) |
-ot | 如果比旧则为真(指 mtime) |
-ef | 如果有硬链接到则为真 |
实例1:检查命令文件/bin/cp是否存在,如果存在则打印找到此文件,否则打印没找到此文件
[root@Master ~]# test -e /bin/cp && echo "The command $_ found." || echo "The command $_ not found." The command /bin/cp found.
上述命令语句中的"$_"表示前一个执行的命令中的最后一个参数。
实例2:检查文件/etc/resolv.conf是否存在,如果存在则打印找到此文件,否则打印没找到此文件:
[root@Master ~]# test -f /etc/resolve.conf && echo "The file $_ found." || echo "The file $_ not found." The file /etc/resolve.conf not found.
实例3:检查目录/local是否存在:
$ test -d /local && echo "The directory $_is exist." || echo "The directory$_ dose not exist." The directory /local dose not exist.
实例4:检查一个文件是否为特殊字符文件:
$ test -c /dev/zero && echo "The file $_ is a character special." || echo "The file $_ not a character sprcial." The file /dev/zero is character sprcial. $test -c /bin/ls && echo "The file $_is a character special." || echo "The file $_ not a character special." The file /bin/ls not a character special.
实例5:检查一个文件是否为特殊块文件:
$test -b /dev/sda && echo "The file $_ is a block special." || echo "The file $_ not a block special." The file /dev/sda is block special. $test -b /bin/cp && echo "The file $_ is a block special." || echo "The file $_ not a block special." The file /bin/cp not a block special.
实例6:检查一个文件是否为一个命名管道:
$ test -p /dev/initctl && echo "The file $_ is a named pipe." || echo "The file $_ not a named pipe." The file /dev/initctl is named pipe. $test -p /bin/grep && echo "The file $_ is a named pipe." || echo "The file $_ not a named pipe." The file /bin/grep not a named pipe.
实例7:检查一个文件是否为一个套接字文件:
$ test -S /dev/log && echo "The file $_ is a socket." || echo "The file $_ not a socket." The file /dev/log is a socket
实例8:检查一个文件是否为符号链接文件:
$ test -L /bin/sh && echo "The file $_ is a symbolic link" || echo "The file $_ not a symbolic link." The file /bin/sh is a symbolic link. $ test -h /bin/sh $$ echo "The file $_ is a symbolic link." || echo "The file $_ not a symbolic link." The file /bin/sh is a symbolic link. ls -l /bin/sh lrwxrwxrwx 1 root root 4may 31 2013 /bin/sh-> bash
实例9:检查一个文件是否设置了sgid位:
$test -g /bin/mount && echo "The file $_ has sgid bit." || echo "The file $_ has no sgid bit." The file /bin/mount has no sgid bit.
实例10:检查一个文件是否设置了suid位:
$test -u /bin/mount && echo "The file &_ has suid bit." || echo "The file $_ has no suid bit." The file /bin/mount has suid bit.
实例11:检查一个文件是否存在且可读取:
$ test -r /proc/meminfo && echo "The file $_ is readable." The file /proc/meminfo is readable.
实例12:检查一个文件是否存在且可写入:
$ test -w /proc/meminfo && echo "The file $_ is writable." || echo "The file $_ is not writable." The file /proc/meminfo is writable.
实例13:检查一个文件是否存在且位可执行文件:
$ test -x /bin/cp && echo "The file $_ is executable." || echo "The file $_ is not executable." The file /bin/cp is executable. $test -x /proc/meminfo && echo 'The file $_ is executable." || echo "The file $_ is not executable." The file /proc/meminfo is not executable.
实例14:检查一个文件是否比另一个文件新:
$touch /tmp/test1 $touch /tmp/test2 $ test /tmp/test1 -nt /tmp/test2 && echo "The file /tmp/test1 is newer than file /tmp/test2." || echo "The file /tmp/test2 is newer than /tmp/test1." The file /tmp/test2 is newer than /tmp/test1.
实例15:检查一个文件是否比另一个文件旧:
$test /tmp/test1 -or /tmp/test2 && echo "The file /tmp/test2 is newer than file /tmp/test1." || echo "The file /tmp/test1 is newer than /tmp/test2." The file /tmp/test2 is newer than file /tmp/test1.
6.1.1.2 字符串测试操作符表:
操作符 | 描述 |
-z | 如果为空则为真 |
-n | 如果不为空则为真 |
= | 如果与相同则为真 |
!= | 如果与不相同则为真 |
< | 如果的字典顺序排在之前则为真(ASCII码顺序) |
> | 如果的字典顺序排在之后则为真(ASCII码顺序) |
下面是使用字符串测试操作符的实例:
[root@Master ~]# test "abc" = "cde" ; echo $? 1 [root@Master ~]# test "abc" != "cde" ; echo $? 0 [root@Master ~]# test "abc" \< "cde" ; echo $? 0 [root@Master ~]# test "abc" \> "cde" ; echo $? 1 [root@Master ~]# [ "abc" \> "cde" ]; echo $? 1 [root@Master ~]# [ -z "" ]; echo $? 0 [root@Master ~]# [ -n "abc" ]; echo $? 0
注意:"<"和">"操作符同样被Shell用于重定向,所以必须使用""字符将其转义,及表示位"<"和">"。
6.1.1.3 用于算术测试的操作符,如下表:
操作符 | 描述 |
-eq | 如果两个数值相等则为真 |
-ne | 如果两个数值不相等则为真 |
-le | 如果小于或等于则为真 |
-ge | 如果大于或等于则为真 |
-lt | 如果小于则为真 |
-gt | 如果大于则为真 |
使用算术测试操作符的实例:
test 5 -eq 5 && echo Yes || echo No test 5 -ne 10 && echo Yes || echo No [ 5 -le 10 ] && echo Yes || echo No [ 5 -ge 10 ] && echo Yes || echo No
在Bash中还有一个"[[ ]]",它是"[ ]"的提高版本,它是一个关键字,而不是一个程序,语法如下所示:
[[ EXPERSSION ]]
但"[[ ]]"仅在Bash、Zsh和Korn Shell 中可用,而"[ ]"几乎可以在任一种Shell中使用。
6.1.2 if结构的语法格式
if结构用于在Shell脚本中进行判定。如果指定的条件为真,则执行指定的命令。if语句的条件判断命令可使用上一节讲述的test命令,或者是可以在运行成功时返回状态码0而失败时返回其它状态码的任何一个命令。if语句的基本语法如下所示:
if TEST-COMMANDS; then CONSEQUENT-COMMANDS; fi
或
if TEST-COMMANDS; then CONSEQUENT-COMMANDS fi
或
if TEST-COMMANDS then CONSEQUENT-COMMANDS fi
注意:if和then若写在同一行,TEST-COMMANDS与then之间要使用分号";"隔开。if语句结构一定要以"fi"结尾。
在上述语法中,TEST-COMMANDS被执行,且如果它的返回状态码为0,则CONSEQUENT-COMMANDS将被执行,否则脚本将直接跳转到if结构之后的语句继续执行。
if语句实例,从标准输入读入输入的密码,并判断密码是否正确:
#!/bin/bash # 显示提示用户输入密码的信息,然后从标准输入隐式地读取用户的输入,并将读取的内容赋值给变量pass read -sp "Enter a password:" pass # 如果变量pass的值为root,则显示密码验证通过的信息,然后退出脚本的执行,退出状态码为0 if test "$pass" == "root" then echo -e "\nPassword verified." exit 0 fi # 退出脚本,退出状态码为1 exit 1
使用read命令读取输入的密码(使用-s选项,标准输入的内容不会打印在终端),并将它存入变量&pass。如果pass的内容不是"root",则跳过if结构,继续执行下一个语句"exit 1"。
6.1.3 实例:if...else...fi语句
if…else…fi语句允许脚本在命令成功或失败的基础上做出选择。即根据条件的不同结果,可以采取不同的行为。if…else…fi语句的语法如下所示:
if TEST-COMMANDS then TEST-COMMANDS is zero (true - 0) #若果条件测试命令为真,执行else语句之前的所有命令 execute all commands up to else statement else if TEST-COMMANDS is not true then #如果条件测试命令为假,执行fi之前的所有命令 execute all commands up to fi fi
现在更新checkpasswd.sh脚本的内容,新的脚本内容如下:
#!/bin/bash # 显示提示用户输入密码的信息,然后从标准输入隐式地读取用户的输入,并将读取的内容赋值给变量pass read -sp "Enter a password:" pass # 如果变量pass的值为root,则显示密码验证通过的信息,然后退出脚本的执行,退出状态码为0 # 否则显示拒绝访问的信息,然后退出脚本的执行,退出状态码为1 if [ "$pass" == "root" ] then echo -e "\nPassword verified." exit 0 else # 显示拒绝访问的信息 echo -e "\nAccess denied." # 退出脚本的执行,退出状态码为1 exit 1 fi
6.1.4 实例:嵌套的if/else语句
你可以在if语句中或else语句中再嵌入一个完整的if…else…fi结构,这被称为嵌套的if/else语句。其语法如下所示:
if TEST-COMMANDS then if TEST-COMMANDS then CONSEQUENT-COMMANDS else CONSEQUENT-COMMANDS fi else CONSEQUENT-COMMANDS fi
或
if TEST-COMMANDS then CONSEQUENT-COMMANDS else if TEST-COMMANDS then CONSEQUENT-COMMANDS else CONSEQUENT-COMMANDS fi fi
实例:
#!/bin/bash # 声明一个整型变量 declare -i count # 显示提示输入一个数值的信息,然后将用户的输入存入变量count中 read -p "Enter an count:" count if [ $count -eq 100 ] then echo "Count is 100." else if [ $count -gt 100 ] then echo "Count is greater than 100." else echo "Count is less than 100." fi fi
6.1.5 实例:多级的if...elif...else...fi
多级的if…elif…else…fi让脚本有多种可能性和条件。if…elif…else…fi语句的语法类似如下所示:
if TEST-COMMANDS then TEST-COMMANDS is zero (true - 0) execute all commands up to elif statement elif TEST-COMMANDS then TEST-COMMANDS is zero (true - 0) execute all commands up to elif statement elif TEST-COMMANDS then TEST-COMMANDS is zero (true - 0) execute all commands up to else statement else None of the above TEST-COMMANDS are true (i.e. all of the above nonzero or false) execute all commands up to fi fi
下面使用if...elif...else...fi语句结构编写一个判断数值是正数还是负数的脚本:
#!/bin/bash # 如果指定的命令行参数个数等于0,则显示必须指定一个参数的提示信息,然后退出脚本,退出状态码为1 if [ $# -eq 0 ] then # 显示必须指定一个参数的提示信息 echo "$0 : You must give/supply one integers." # 退出脚本,退出状态码为1 exit 1 fi # 如果指定的参数大于0 if [ $1 -gt 0 ] then echo "Then number is positive." # 如果指定的参数小于0 elif [ $1 -lt 0 ] then echo "Then number is negative." # 如果指定的参数等于0 elif [ $1 -eq 0 ] then echo "The number is 0." else echo "Opps $1 is not number, give number." fi
6.2 条件执行
在Bash下,你可以根据最后一个命令的退出状态使用条件执行来连接两个命令。这对于控制命令执行的顺序是很有用的。当然,你也可以在if语句中使用条件执行。Bash支持一下两种条件执行:
逻辑与:只有当前一个命令执行成功时才执行后一个命令。
逻辑或:只有当前一个命令执行失败时才执行后一个命令。
6.2.1 实例:逻辑与"&&"
逻辑与“&&”是一个布尔操作符。其语法如下所示:
command1 && command2
只有当command1返回一个退出状态码0时,command2才会执行。换句话说,就是只有当command1执行成功,才会执行command2。
例如:
rm somefile && echo "File is deleted."
只有当rm命令执行成功时,才运行echo命令。
在文件/etc/passwd中查找指定的账号:
grep "^li" /etc/passwd && echo "The account found in /etc/passwd"
我们也可以使用逻辑与操作符&&在if语句中将多个test命令连接在一起。例如:
if [ -n $var ] && [ -e $var ] then echo "\$var is not null and a file named $var exists." fi
或者
if [[ -n $var && -e $var]] then echo "\$var is not null and a file named $var exists." fi
下面我们通过一个脚本来学习逻辑与操作符&&在if条件语句中的用法,脚本的内容如下所示:
#! /bin/bash # 如果指定的命令行参数个数不为1,则打印脚本的使用方法信息并退出脚本的运行,退出状态码为1 if [ $# -ne 1 ] then echo "Usage: 'basename $0' Number" exit 1 fi # 将第一个命令行参数赋值给变量num num=$1 # 如果$num的值大于等于90且小于100, 则执行if语句中的内容,否则执行下面的elif语句 if [ "$num" -ge 90 ] && [ "$num" -lt 100 ] then echo "Excellent!" elif [ "$num" -ge 80 ] && [ "$num" -lt 90 ] then echo "Good!" elif [ "$num" -ge 60 ] && [ "$num" -lt 80 ] then echo "Pass mark!" elif [ "$num" -ge 0 ] && [ "$num" -lt 60 ] then echo "Fail!" else echo "Wrong number!" fi
为脚本增加执行权限:
chmod u+x compareNumber.sh
执行脚本:
bash compareNumber.sh 90
如果上述脚本中我们使用"[[ ]]"替换"[ ]",那么脚本的内容会简洁一些,改进后的脚本为compareNumber_improve.sh,其内容如下所示:
#! /bin/bash # 如果指定的命令行参数个数不为1,则打印脚本的使用方法信息并退出脚本的运行,退出状态码为1 if [ $# -ne 1 ] then echo "Usage: 'basename $0' Number" exit 1 fi # 将第一个命令行参数赋值给变量num num=$1 # 如果$num的值大于等于90且小于100, 则执行if语句中的内容,否则执行下面的elif语句 if [[ "$num" -ge 90 && "$num" -lt 100 ]] then echo "Excellent!" elif [[ "$num" -ge 80 && "$num" -lt 90 ]] then echo "Good!" elif [[ "$num" -ge 60 && "$num" -lt 80 ]] then echo "Pass mark!" elif [[ "$num" -lt 60 && "$num" -ge 0 ]] then echo "Fail!" else echo "Wrong number!" fi
在test命令中我们还可以使用-a选项表示逻辑与,这样前面的例子就可以写成:
if [ -n $var -a -e $var ] then echo "\$var is not null and a file named $var exists." fi
但是从代码的可移植性和可读性,以及运行效率方面考虑,我们通常都应该避免使用-a选项。
6.2.2 实例:逻辑或"||"
逻辑或“||”也是一个布尔操作符。其语法如下所示:
command1 || command2
只有当command1返回非0状态时,才运行command2。换句话说,就是只有当command1执行失败,才会执行command2。
我们看下面这条命令:
grep "^li" /etc/passwd || echo "User not found in /etc/passwd"
上例中,在文件/etc/passwd中查找账号li,若没有找到则打印"User not found in /etc/passwd"。
我们可以将逻辑与“&&”和逻辑或“||”联合使用:
test $(id -u) -eq 0 && echo "You are root" || echo "You are not root"
或
test 'id -u' -eq 0 && echo "You are root" || echo "You are not root"
我们也可以使用逻辑或操作符“||”在if语句中将多个test命令连接在一起。比如,下面的if语句:
if [ "$var" -eq 98 ] || [ "$var" -eq 47 ] || [ "$var" -eq 68 ] then echo "Test succeeds." else echo "Test fails." fi
或者
if [[ "$var" -eq 98 || "$var" -eq 47 || "$var" -eq 68 ]] then echo "Test succeeds." else echo "Test fails." fi
如果if条件语句中变量$var的值等于98或者47或者68,那么就执行if语句中的内容,否则执行else语句中的内容。
下面我们再来通过一个脚本checkDaysOfWeek.sh来学习逻辑操作符“||”在if条件语句中的用法,脚本内容如下所示:
#!/bin/bash NOW='date +%a' # 如果今天是星期一或星期三,则打印信息"Please run full backup!",否则执行下面的elif语句 if [ "$NOW" = "Mon" ] || [ "$NOW" = "Sat" ] then echo "Please run full backup!" elif [ "$NOW" = "Tue" ] || [ "$NOW" = "Wed" ] || [ "$NOW" = "Thu" ] || [ "$NOW" = "Fri" ] then echo "Please run incremental backup!" elif [ "$NOW" = "Sun" ] then echo "Don't need to bachup!" else echo "Wrong day!" fi
上述脚本中,我们可以使用"[[ ]]" 代替"[ ]",与test命令的-a选项类似,也可以使用test命令的-o选项表示逻辑或。通常都应该避免使用test命令的-o选项。
6.2.3 实例:逻辑非"!"
逻辑非‘!’同样是布尔操作符。它用于测试表达式是否为真或假。其语法如下所示:
! expression
此操作符可以直接在test命令中使用。比如检查一个文件是否存在:
test ! -f /etc/resolv.conf && echo "File /etc/resolv.conf not found."
如果文件/etc/resolv.conf 不存在,则打印"File /etc/resolv.conf not found."。若一个目录不存在则创建此目录:
[ ! -d /home/li ] && mkdir /home/li || echo "The directory is exist."
我们也可以在if条件语句中使用逻辑非操作符。比如:
if [ ! -d /home/li] then mkdir /home/li else echo "The directory is exist." fi
6.3 case语句实例
case语句是多级的if…then…else…fi语句很好的替代方式。它可以让一个条件与多个模式相比较,而且case语句结构的读写比较方便。
语法中的表达式EXPRESSION会依次与每一个模式PATTERNn相比较,如果有匹配的模式项,则与该模式项相关联的命令列表CONSEQUENT-COMMANDS将被执行。
case语句的语法如下所示: case EXPRESSION in PATTERN1 ) CONSEQUENT-COMMANDS ;; PATTERN2 ) CONSEQUENT-COMMANDS ;; PATTERN3 | PATTERN4 ) CONSEQUENT-COMMANDS ;; … PATTERNn) CONSEQUENT-COMMANDS ;; esac
PS:case语句结构一定要以“esac”结尾。每一个命令列表都以两个分号“;;”为终结,只有最后一个命令列表的(即esac语句之前的)“;;”可以被省略。
语法中的表达式EXPRESSION会依次与每一个模式PATTERNn相比较,如果有匹配的模式项,则与该模式项相关联的命令列表CONSEQUENT-COMMANDS将被执行。
下面请看例子:
#如果指定给脚本的命令行参数的数目小于2,则显示脚本的使用方法信息并退出 if [ $# -lt 2 ] then echo "Usage : $0 signalnumber pid" exit fi case "$1" in 1) echo "Sending SIGHUP signal to PID $2." #向指定的PID发送SIGHUP信号 kill -SIGHUP $2 ;; 2) echo"Sending SIGINT signal to PID $2. # "向指定的PID发送SIGINT信号 kill -SIGINT $2 ;; 3) echo "Sending SIGQUIT signal to PID $2." #向指定的PID发送SIGQUIT信号 kill -SIGQUIT $2 ;; 9) echo "Sending SIGKILL signal to PID $2." #向指定的PID发送SIGKILL信号 kill -SIGKILL $2 ;; *) echo "Signal number $1 is not processed." ;; esac
运行此脚本的结果如下:
$ sleep 60 & [1] 8616 $ ./killsignal.sh 10 8616 Signal number 10 is not processed. $ ./killsignal.sh 9 8616 Sending SIGKILL signal to PID 8616 [1]+ Killed sleep 60
上述脚本中,特殊变量$1和$2分别是指定的信号值和进程值。使用Kill命令,它会发送相应的信号到指定的进程。最后一个模式匹配项“*)”表示的是默认匹配项,即表示若脚本中“1),2),3),9)”都没有被匹配,则匹配此项。
下面我们再看一个使用多重模式匹配的case语句的脚本:
#得出今天是星期几 NOW='date +%a' case $NOW in #若今天为星期一 Mon) echo "Full backup" ;; #若今天为星期二、星期三、星期四或星期五 Tue | Wed | Thu | Fri) echo "Partial backup" ;; #若今天为星期六或星期日 Sat | Sun) echo "No backup" ;; *);; esac
6.4 小结
下面我们总结一下本章所学的主要知识:
- test命令允许你做各种测试并每当测试成功或失败时设置它的退出状态码为0(表示真)或1(表示假)。
- test命令可以用于文件属性测试、字符串测试和算术测试。
- if结构用于在Shell脚本中进行判定。如果指定的条件为真,则执行指定的命令。
- if语句的条件判断命令可使用test命令,或者是可以在运行成功时返回状态码0而失败时返回其它状态码的任何一个命令。if语句结构一定要以“fi”结尾。
- if…else…fi语句允许脚本在命令成功或失败的基础上做出选择。
- 嵌套的if/else语句是可以在if语句中或else语句中再嵌入一个完整的if…else…fi结构。
- 多级的if…elif…else…fi让脚本有多种可能性和条件。
- 逻辑与“&&”是一个布尔操作符,只有当前一个命令执行成功时才执行后一个命令。
- 逻辑或“||”同样是一个布尔操作符,只有当前一个命令执行失败时才执行后一个命令。
- case语句是多级的if…then…else…fi语句很好的替代方式。case语句结构一定要以“esac”结尾。每一个命令列表都以两个分号“;;”为终结,只有最后一个命令列表的(即esac语句之前的)“;;”可以被省略。