接上一节课问题,系统信息生成器脚本如何适应运行脚本的用户权限呢?我们要在脚本中赋予新的功能,即根据测试结果来改变脚本执行的方向。用编程术语来说,就是要求脚本有分支功能。
使用if
if语句的语法格式如下:
if commands;then
commands
elif commands;then
commands
else
commands
fi
退出状态
命令(自己编写的脚本、函数)执行完毕后都会向操作系统发送一个数值,称之为“退出状态”。这个值是一个0~255的整数,用来表示命令执行成功还是失败。数值0表示执行成功,其他数值表示执行失败。
有些命令使用不同的退出值来诊断不同的错误,可以在对应命令man手册中Exit Status段落中查看详细的命令退出值描述。
shell提供了一个可以检测退出状态的参数 $?
shell提供了两个内置命令,除了以一个0或1退出状态来结束执行。true命令总是表示执行成功,false命令总是表示执行失败。
#!/bin/bash
true
echo $? # 输出退出状态值 0
false
echo $? # 输出退出状态值 1
if语句真正做的事情是评估命令的成功或者失败。
#!/bin/bash
if true;then echo "It's true";fi # 输出It's true
if false;then echo "It's true";fi # 不会输出任何信息
# 当if命令后有一系列的命令,那么则根据最后一个命令的执行状态来进行评估
if false;true;then echo "It's true";fi # 输出It's true
if true;false;then echo "It's true";fi # 不会输出任何信息
使用test命令
经常和if一起使用的命令是test。test会执行各种检查和比较。该命令有两种等价形式
test expression
与
[ expression ]
这里expression是一个表达式,其结果是true或者false。当为true时,test命令返回一个0退出状态;当表达式结果为false时,test命令的退出状态是1。
文件表达式
文件表达式用于评估文件的各类状态
表达式 | 成为true的条件 |
file1 -ef file2 | file1和file2拥有相同的信息节点编号(这两个文件通过硬链接指向同一个文件) |
file1 -nt file2 | file1比file2新 |
file1 -ot file2 | file1比file2旧 |
-b file | file存在并且是一个块(设备)文件 |
-c file | file存在并且是一个字符(设备)文件 |
-d file | file存在并且是一个目录 |
-e file | file存在 |
-f file | file存在并且是一个普通文件 |
-L file | file存在并且是一个符号链接 |
-p file | file存在并且是一个命名管道 |
-r file | file存在并且可读 |
-w file | file存在并且可写 |
-x file | file存在并且可执行 |
-s file | file存在并且其长度大于0 |
下列脚本可用来演示某些文件表达式
#!/bin/bash
# test-file:Evaluate the status of a file
FILE=~/.bashrc
if [ -e "$FILE" ];then
if [ -f "$FILE" ];then
echo "$FILE is a regular file."
fi
if [ -d "$FILE" ];then
echo "$FILE is a directory."
fi
if [ -r "$FILE" ];then
echo "$FILE is readable."
fi
if [ -w "$FILE" ];then
echo "$FILE is writable."
fi
if [ -x "$FILE" ];then
echo "$FILE is executable/searchable."
fi
else
echo "$FILE does not exist."
exit 1
fi
exit
以上脚本会评估赋值给常量FILE的文件,并显示评估结果。
类似的,通常在return命令中包含一个整数参数,shell函数可以返回一个退出状态。把上述脚本转换为一个shell函数,从而能够在一个更大的程序中使用,可以将exit命令替换为return命令。
test_file(){
# test-file:Evaluate the status of a file
FILE=~/.bashrc
if [ -e "$FILE" ];then
if [ -f "$FILE" ];then
echo "$FILE is a regular file."
fi
if [ -d "$FILE" ];then
echo "$FILE is a directory."
fi
if [ -r "$FILE" ];then
echo "$FILE is readable."
fi
if [ -w "$FILE" ];then
echo "$FILE is writable."
fi
if [ -x "$FILE" ];then
echo "$FILE is executable/searchable."
fi
else
echo "$FILE does not exist."
return 1
fi
}
字符串表达式
表达式 | 成为true的条件 |
string | string不为空 |
-n string | string长度大于0 |
-z string | string长度等于0 |
string1=string1 string1==string2 | string1和string2相等。单等号和多等号都可以使用,但是双等号使用的更多 |
string1!=string2 | string1和string2不相等 |
#!/bin/bash
# test-string: evaluate the value of a string
ANSWER=maybe
if [ -z "$ANSWER" ];then
echo "There is no answer" >&2
exit 1
fi
if [ "$ANSWER" = "yes" ];then
echo "The answer is YES."
elif [ "$ANSWER" = "no" ];then
echo "The answer is NO."
elif [ "$ANSWER" = "maybe" ];then
echo "The answer is MAYBE."
else
echo "The answer is UNKONWN."
fi
通过使用elif,我们可以建立一个更复杂的逻辑测试。
整数表达式
整数判断操作
表达式 | 成为true的条件 |
integer1 -eq integer2 | integer1和integer2相等 |
integer1 -ne integer2 | integer1和integer2不相等 |
integer1 -lt integer2 | integer1小于integer2 |
integer1 -gt integer2 | integer1大于integer2 |
integer1 -le integer2 | integer1小于等于integer2 |
integer1 -ge integer2 | integer1大于等于integer2 |
一个演示整数表达式的脚本
#!/bin/bash
#test-integer:evaluate the value of an integer
INT=-5
if [ -z "$INT" ];then
echo "INT is empty." >&2
exit 1
fi
if [ $INT -eq 0 ];then
echo "INT is zero."
else
if [ $INT -lt 0 ];then
echo "INT is negative."
else
echo "INT is positive."
fi
if [ $((INT%2)) -eq 0 ];then
echo "INT is even."
else
echo "INT is odd."
fi
fi
增强的test命令
语法格式:
[[ expression ]]
expression是一个表达式,其结果为true或false。[[ ]]命令和[ ]功能类似,[ ]的表达式它都能支持。
除此之外,[[ expression ]]有两个更显著的特性:
- 增加了一个新的字符串表达式 string1=~regexp;
如果string1与正则表达式regexp相匹配,则返回true。该特性为执行数据验证功能提供了许多可能性。上面的脚本程序,如果常量INT包含整数以外的其他值,脚本就会执行失败。
可以使用[[ ]]和=~字符串表达式操作符 解决以上问题#!/bin/bash # test-integer2: evaluate the value of an integer INT=~-5 if [[ "$INT" =~ ^-?[0-9]+$ ]];then if [ $INT -eq 0 ];then echo "INT is zero." else if [ $INT -lt 0 ];then echo "INT is negative." else echo "INT is positive." fi if [ $((INT % 2)) -eq 0 ];then echo "INT is even." else echo "INT is odd." fi fi else echo "INT is not an integer." >&2 exit 1 fi
起到了限制只能是整数并且同样消除了空值的可能性。
- ==操作符支持模式匹配。
该特性在评估文件或者路径名时非常有用#!/bin/bash FILE=foo.bar if [[ $FILE == foo.* ]];then echo "$FILE match pattern 'foo.*'" fi
(())为整数设计
(())专为操作整数的复合命令。该命令支持一套完整的算术计算。
使用(())可以简化上面的脚本,注意观察区别
#!/bin/bash
# test-integer2a: evaluate the value of an integer
INT=-5
if [[ "$INT" =~ ^-?[0-9]+$ ]];then
if ((INT == 0));then
echo "INT is zero."
else
if ((INT < 0));then
echo "INT is negative."
else
echo "INT is positive."
fi
if (( ((INT % 2)) == 0 ));then
echo "INT is even."
else
echo "INT is odd."
fi
fi
else
echo "INT is not an integer" >&2
exit 1
fi
(())直接用小于号、大于号、等号用来测试相等性;
(())复合命令只能处理整数,所以它可以通过名字来识别变量,不需要执行变量扩展操作。
组合表达式
可以将表达式组合起来,执行更严格的真值测试。组合表达式是通过逻辑运算符组合起来的。
test和[[ ]]使用不同的操作符来表示NOT AND OR这三种逻辑运算符。
Operation | test - [ ] | [[]]和(()) |
AND | -a | && |
OR | -o | || |
NOT | ! | ! |
下面一个脚本用来检测一个整数是否属于某个范围内的值
#!/bin/bash
# test-integer3: determine if an integer is within a
# specified range of values.
MIN_VAL=1
MAX_VAL=100
INT=50
if [[ "$INT" =~ ^-?[0-9]+$ ]];then
if [[ $INT -ge $MIN_VAL && $INT -le $MAX_VAL ]];then
# if [[ INT -ge MIN_VAL && INT -le MAX_VAL ]];then
# if [[ INT > MIN_VAL && INT < MAX_VAL ]];then
# if (( INT > MIN_VAL && INT < MAX_VAL ));then
# 用test形式组合表达式
# if [ $INT -ge $MIN_VAL -a $INT -le $MAX_VAL ]];then
echo "$INT is within $MIN_VAL to $MAX_VAL"
else
echo "$INT is out of range."
fi
else
echo "INT is not an integer" >&2
exit 1
fi
! NOT运算符对表达式的运算结果取反。
下面的脚本将修改判断逻辑,来判断INT的值是否在指定的范围之外
#!/bin/bash
# test-integer4: determine if an integer is outside a
# specified range of values.
MIN_VAL=1
MAX_VAL=100
INT=50
if [[ "$INT" =~ ^-?[0-9]+$ ]];then
if [[ ! (INT -ge MIN_VAL && INT -le MAX_VAL) ]];then
# if [ ! \( $INT -ge $MIN_VAL -a $INT -le $MAX_VAL \) ];then
echo "$INT is outside $MIN_VAL to $MAX_VAL"
else
echo "$INT is in range."
fi
else
echo "INT is not an integer" >&2
exit 1
fi
以上脚本有几点需要注意的:
- 组合表达式取反,需要把组合表达式用圆括号括起来,否则根据优先级,!取反只对第一个表达式取反操作,再与第二个表达式取and操作;
- [[ ]]中圆括号直接把组合表达式括起来即可。但是对于test需要对()进行转义操作
test和[[]]命令功能基本一致。哪一个更好呢?test更具通用性,属于POSIX的一部分,[[]]是bash特定的,更容易编码,更易于理解。
控制运算符-另一种方式的分支
command1 && command2
对于 && ,先执行command1,只有在command1执行成功时,command2才能够执行。
从实用性考虑,mkdir temp && cd temp
command1 || command2
对于 || ,先执行command1,只有在command1执行失败时,command2才能够执行。
[ -d temp] || mkdir temp 判断是否存在目录temp,如果不存在,才会执行创建目录的命令。
这种构造类型可以轻松处理脚本中的错误。我们可以在脚本中这样编辑,[ -d temp ] || exit 1 如果脚本需要目录temp,而这个目录不存在,则这个脚本会终止,并且退出状态为1。
学会了流程控制中的IF分支语句,我们就可以对系统信息报告生成器脚本做以下修正
#!/bin/bash
# Program to output a system information page
TITLE="Syatem Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")
TIME_STAMPT="Generated $CURRENT_TIME,by $USER"
report_uptime () {
cat <<- _EOF_
<h2>System Uptime</h2>
<pre>$(uptime)</pre>
_EOF_
return
}
report_disk_space () {
cat <<- _EOF_
<h2>Disk Space Utilization</h2>
<pre>$(df -h)</pre>
_EOF_
return
}
# 主要对report_home_space方法进行执行用户是否具有
# 读取所有主目录的权限判断
report_home_space () {
if [[ $(id -u) -eq 0 ]];then
cat <<- _EOF_
<h2>Home Space Utilization (All Users)</h2>
<pre>$(du -sh /home/*)</pre>
_EOF_
else
cat <<- _EOF_
<h2>Home Space Utilization ($USER)</h2>
<pre>$(du -sh $HOME)</pre>
_EOF_
fi
return
}
cat <<- _EOF_
<html>
<head>
<title>$TITLE</title>
</head>
<body>
<h1>$TITLE</h1>
<p>$TIME_STAMPT</p>
$(report_uptime)
$(report_disk_space)
$(report_home_space)
</body>
</html>
_EOF_
可以关注作者微信公众号,追踪更多有价值的内容!