命令流程通常有三种 —— 顺序、分支及循环,顺序没啥好说的,流程控制的重点在于分支和循环结构。
在了解这两个结构之前,补充一个知识点 —— 退出状态码
一、 退出状态码
1. 含义及查看方法
shell中运行的每个命令其实都有退出状态码,退出状态码是0~255之间的整数值,在命令结束时传给shell,标志命令是否正常执行。0表示正常,其余数字表示各种各样的异常,常见如下:
状态码 | 描述 |
0 | 命令成功结束 |
1 | 一般性未知错误 |
2 | 不适合的shell命令 |
126 | 命令不可执行 |
127 | 没找到命令 |
128 | 无效的退出 |
128+x | 与Linux信号x相关的严重错误 |
130 | 通过ctrl+c终止的命令 |
255 | 正常范围之外的退出状态码 |
Linux提供了一个专门的变量$?保存最近一个已执行命令的退出状态码
echo $?
2. 设置方法
默认情况下,shell脚本会以最后一个命令的退出状态码退出,也可以使用exit命令自己设置。设置范围应该在0~255之间,如果大于255,返回的数值会是 设置值%256得到的结果。
可以稍微改下前面的脚本
#!/bin/bash
var1=10.46
var2=43.67
var3=33.2
var4=71
var5=$(echo "scale=4; $var1*$var2+$var3*$var4" | bc)
echo The final answer is $var5
exit 5
还可以使用变量作为exit的参数
#!/bin/bash
var1=10.46
var2=43.67
var3=33.2
var4=71
var5=$(echo "scale=4; $var1*$var2+$var3*$var4" | bc)
echo The final answer is $var5
exit $var4
下面正式来看分支结构
二、 if命令
1. 格式与简单用法
if分支的结构如下所示
#格式1
if commands
then
commands
elif commands
then
commands
else
commands
fi
#格式2,then与if在同一行,注意有分号
if commands;then
commands
elif commands;then
commands
else
commands
fi
与一般编程语言使用表达式判断不同,shell的if可以使用命令,原理就是使用前面的退出状态码,如果状态码为0则执行then后面的语句。例如
#!/bin/bash
myuser=oracle
if grep $myuser /etc/passwd
then
echo "The user $myuser exists on the system"
else
echo "The user $myuser doesn't exist on the system"
fi
这种直接在if中判断命令的其实用的也不多,注意还是使用表达式判断,常见有以下三种:
- 数值判断
- 字符串判断
- 文件判断
2. 数值判断
写法较多,每种也有各自优缺点:
- 方法一: if [ $A -lt $B ]; then ...
-eq(等于)、-ne(不等于)、lt(小于)、gt(大于)、le(小于等于)、ge(大于等于);缺点:只能比较整数,使用lt,gt等不直观
- 方法二: if ((A < B)) then ...
优点:简单易理解,允许使用高级数学表达式(例如++、--、位运算、与或非等);缺点:还是只能比较整数
- 方法三: if (echo $A $B | awk '!($1>$2){exit 1}') then ...
使用awk比较,优点:可以比较小数;缺点:表达式复杂,难记
- 方法四: if (echo $A - $B | bc -q | grep -q "^-"); then ...
使用bc比较,优点:可以比较小数;缺点:表达式更复杂,难记
以下例子判断指定变量是否为偶数
#!/bin/bash
INT=-5
if [[ ${INT} =~ ^-?[0-9]+$ ]];then #为整数
if [ ${INT} -eq 0 ];then
echo "the INT is zero."
else
if [ ${INT} -lt 0 ];then
echo "the INT is negative."
else
echo "the INT is positive."
fi
if [ $((INT % 2)) -eq 0 ];then
echo "the INT is even."
else
echo "the INT is odd."
fi
fi
else
echo "the INT is not an integer."
exit 1
fi
也可以用上面的方法二
#!/bin/bash
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 postive"
fi
if (( ((INT % 2)) == 0));then
echo "INT is even"
else
echo "INT is odd"
fi
fi
else
echo "INT is not aninteger"
exit 1
fi
3. 字符串判断
- =或==:等于,if [ "$a" = "$b" ] 或 if [ "$a" == "$b" ]
注意==在[[]]和[]中的行为是不同的,[[]]提供模式匹配功能
1. [[ $a == z* ]] # 如果$a以"z"开头(模式匹配)那么将为true
2. [[ $a == "z*" ]] # 如果$a等于z*(字符匹配)那么结果为true
3. [ $a == z* ] # 将会发生File globbing 和word splitting
4. [ "$a" == "z*" ] # 如果$a等于z*(字符匹配)那么结果为true
- != 不等于,if [ "$a" != "$b" ], 这个操作符将在[[]]结构中使用模式匹配.
- < 小于,根据ASCII字母顺序比较大小,if [[ "$a" < "$b" ]] 或 if [ "$a" \< "$b" ] (在[]结构中"<"需要被转义)
- > 大于,根据ASCII字母顺序比较大小,if [[ "$a" > "$b" ]] 或 if [ "$a" \> "$b" ] (在[]结构中"<"需要被转义)
- -z 字符串为空或长度为0
- -n 字符串不为空、长度不为0
#!/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 unknown."
fi
4. 文件判断
- -e 判断对象是否存在
- -d 判断对象是否存在,并且为目录
- -f 判断对象是否存在,并且为常规文件
- -L 判断对象是否存在,并且为符号链接
- -h 判断对象是否存在,并且为软链接
- -s 判断对象是否存在,并且长度不为0(即文件是否不为空)
- -r 判断对象是否存在,并且可读
- -w 判断对象是否存在,并且可写
- -x 判断对象是否存在,并且可执行
- -O 判断对象是否存在,并且属于当前用户
- -G 判断对象是否存在,并且属于当前用户组
- -nt 判断file1是否比file2新 [ "/data/file1" -nt "/data/file2" ]
- -ot 判断file1是否比file2旧 [ "/data/file1" -ot "/data/file2" ]
#!/bin/bash
# test-file: Evaluate the status of a file
FILE=~/.bashrc
if [ -e "$FILE" ];then //-e判断是否存在这个文件
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"
fi
else
echo "$FILE does not exist"
exit 1
fi
exit
着重说一下最后的exit,exit后面不带参数,默认退出状态为0,如果这个命令出现在脚本的最后一行,默认退出状态以0终止,但是上面这个脚本如果判断出文件不存在,直接就以1退出了,不会运行最后一行的exit
如果我们把上面的shell脚本里的内容变成一个函数,同时要求当目标文件不存在的时候返回状态值1
test_file () {
if ...
...
...
else
echo "$FILE does not exist"
return 1
fi
}
5. 复合条件判断
即与、或、非
判断符号 | 含义 | 举例 |
-a 或 && | 逻辑与,前面的表达式为真才会执行后面的代码 | [ 1 -eq 1 -a 1 -ne 0 ] 或者 [ 1 -eq 1 ] && [ 1 -ne 0 ] |
-o 或 || | 逻辑或,前面的表达式为假才会执行后面的代码 | [ 1 -eq 1 -o 1 -ne 0 ] 或者 [ 1 -eq 1 ] || [ 1 -ne 0 ] |
! | 逻辑非,对表达式取反 | if [ ! 表达式 ] |
例1:
#!/bin/bash
MIN_VAL=1
MAX_VAL=100
INT=50
if [[ ${INT} =~ ^-?[0-9]+$ ]];then
if [[ INT -ge MIN_VAL && 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
例2
#!/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 -gt ${MIN_VAL} -a $INT -lt ${MAX_VAL} \) ]; then
echo "$INT is not 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
三、 case命令
多分支判断时简化if语句,判断类型同上。语法格式如下:
#可以使用或 |,这对于处理大小写字符特别方便,模式的书写类似于q|Q)
case variables in
variables 1 | variables 2)
command 1;;
variables 3)
command 2;;
variables 4)
command 3;;
*)
default command;;
esac
使用案例
#!/bin/bash
clear
echo "
please select:
1. display disk space
2. display system information
3. display home space utilization
0. quit
"
read -p "enter selection [0-3] >"
case $REPLY in # REPLY是提供给read命令的默认变量
0) echo "program terminated.";;
1) df -h;;
2) echo "HOSTNAME:$HOSTNAME";;
3) if [ $(id -u) -eq 0 ];then
du -sh /home/*
else
du -sh $HOME
fi;;
*)
echo "invalid entry."
exit 1;;
esac