1.if 结构
本章介绍 Bash 脚本的条件判断语法。
if
是最常用的条件判断结构,只有符合给定条件时,才会执行指定的命令,它的语法如下:
if commands; then
commands
[elif commands; then
commands...]
[else
commands]
fi
这个命令分成三个部分:if
、elif
和else
。其中,后两个部分是可选的,fi
关键字作为结束。
if
关键字后面是主要的判断条件,elif
用来添加在主条件不成立时的其他判断条件,else
则是所有条件都不成立时要执行的部分。
if test $USER = "foo"; then
echo "Hello foo."
else
echo "You are not foo."
fi
上面的例子中,判断条件是环境变量$USER
是否等于foo
,如果等于就输出Hello foo.
,否则输出其他内容。
if
和then
写在同一行时,需要分号分隔。分号是 Bash 的命令分隔符。它们也可以写成两行,这时不需要分号。
if true
then
echo 'hello world'
fi
if false
then
echo 'it is false' # 本行不会执行
fi
上面的例子中,true
和false
是两个特殊命令,前者代表操作成功,后者代表操作失败。if true
意味着命令部分总是会执行,if false
意味着命令部分永远不会执行。
除了多行的写法,if
结构也可以写成单行。
if true; then echo 'hello world'; fi
if false; then echo "It's true."; fi
注意,if
关键字后面也可以是一条命令,该条命令执行成功(返回值0
),就意味着判断条件成立。
if echo 'hi'; then echo 'hello world'; fi
上面命令中,if
后面是一条命令echo 'hi'
。该命令会执行,如果返回值是0
,则执行then
的部分:
if
后面可以跟任意数量的命令。这时,所有命令都会执行,但是判断真伪只看最后一个命令,即使前面所有命令都失败,只要最后一个命令返回0
,就会执行then
的部分。
if false; true; then echo 'hello world'; fi
上面例子中,if
后面有两条命令(false;true;
),第二条命令(true
)决定了then
的部分是否会执行:
elif
部分可以有多个:
#!/bin/bash
echo -n "输入一个1到3之间的数字(包含两端)> "
read character
if [ "$character" = "1" ]; then
echo 1
elif [ "$character" = "2" ]; then
echo 2
elif [ "$character" = "3" ]; then
echo 3
else
echo 输入不符合要求
fi
上面例子中,如果用户输入3
,就会连续判断3次。
2.test 命令
if
结构的判断条件,一般使用test
命令,有三种形式。
# 写法一
test expression
# 写法二
[ expression ]
# 写法三
[[ expression ]]
上面三种形式是等价的,但是第三种形式还支持正则判断,前两种不支持。
上面的expression
是一个表达式。这个表达式为真,test
命令执行成功(返回值为0
);表达式为伪,test
命令执行失败(返回值为1
)。
注意,第二种和第三种写法,[
和]
与内部的表达式之间必须有空格。
test -f /etc/hosts
[ -f /etc/hosts ]
上面的例子中,test
命令采用两种写法,判断/etc/hosts
文件是否存在,这两种写法是等价的。命令执行后,返回值为0
,表示该文件确实存在:
实际上,[
这个字符是test
命令的一种简写形式,可以看作是一个独立的命令,这解释了为什么它后面必须有空格。
下面把test
命令的三种形式,用在if
结构中,判断一个文件是否存在。
# 写法一
if test -e /tmp/foo.txt ; then
echo "Found foo.txt"
fi
# 写法二
if [ -e /tmp/foo.txt ] ; then
echo "Found foo.txt"
fi
# 写法三
if [[ -e /tmp/foo.txt ]] ; then
echo "Found foo.txt"
fi
3.判断表达式
if
关键字后面,跟的是一个命令。这个命令可以是test
命令,也可以是其他命令。命令的返回值为0
表示判断成立,否则表示不成立。因为这些命令主要是为了得到返回值,所以可以视为表达式。
常用的判断表达式有下面这些。
3.1文件判断
以下表达式用来判断文件状态。
[ -a file ]
:如果 file 存在,则为true
。[ -b file ]
:如果 file 存在并且是一个块(设备)文件,则为true
。[ -c file ]
:如果 file 存在并且是一个字符(设备)文件,则为true
。[ -d file ]
:如果 file 存在并且是一个目录,则为true
。[ -e file ]
:如果 file 存在,则为true
。[ -f file ]
:如果 file 存在并且是一个普通文件,则为true
。[ -g file ]
:如果 file 存在并且设置了组 ID,则为true
。[ -G file ]
:如果 file 存在并且属于有效的组 ID,则为true
。[ -h file ]
:如果 file 存在并且是符号链接,则为true
。[ -k file ]
:如果 file 存在并且设置了它的“sticky bit”,则为true
。[ -L file ]
:如果 file 存在并且是一个符号链接,则为true
。[ -N file ]
:如果 file 存在并且自上次读取后已被修改,则为true
。[ -O file ]
:如果 file 存在并且属于有效的用户 ID,则为true
。[ -p file ]
:如果 file 存在并且是一个命名管道,则为true
。[ -r file ]
:如果 file 存在并且可读(当前用户有可读权限),则为true
。[ -s file ]
:如果 file 存在且其长度大于零,则为true
。[ -S file ]
:如果 file 存在且是一个网络 socket,则为true
。[ -t fd ]
:如果 fd 是一个文件描述符,并且重定向到终端,则为true
。 这可以用来判断是否重定向了标准输入/输出/错误。[ -u file ]
:如果 file 存在并且设置了 setuid 位,则为true
。[ -w file ]
:如果 file 存在并且可写(当前用户拥有可写权限),则为true
。[ -x file ]
:如果 file 存在并且可执行(有效用户有执行/搜索权限),则为true
。[ FILE1 -nt FILE2 ]
:如果 FILE1 比 FILE2 的更新时间更近,或者 FILE1 存在而 FILE2 不存在,则为true
。[ FILE1 -ot FILE2 ]
:如果 FILE1 比 FILE2 的更新时间更旧,或者 FILE2 存在而 FILE1 不存在,则为true
。[ FILE1 -ef FILE2 ]
:如果 FILE1 和 FILE2 引用相同的设备和 inode 编号,则为true
。
下面是一个示例。
#!/bin/bash
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
上面代码中,$FILE
要放在双引号之中,这样可以防止变量$FILE
为空,从而出错。因为$FILE
如果为空,这时[ -e $FILE ]
就变成[ -e ]
,这会被判断为真。而$FILE
放在双引号之中,[ -e "$FILE" ]
就变成[ -e "" ]
,这会被判断为伪。
3.2字符串判断
以下表达式用来判断字符串。
[ string ]
:如果string
不为空(长度大于0),则判断为真。[ -n string ]
:如果字符串string
的长度大于零,则判断为真。[ -z string ]
:如果字符串string
的长度为零,则判断为真。[ string1 = string2 ]
:如果string1
和string2
相同,则判断为真。[ string1 == string2 ]
等同于[ string1 = string2 ]
。[ string1 != string2 ]
:如果string1
和string2
不相同,则判断为真。[ string1 '>' string2 ]
:如果按照字典顺序string1
排列在string2
之后,则判断为真。[ string1 '<' string2 ]
:如果按照字典顺序string1
排列在string2
之前,则判断为真。
注意,test
命令内部的>
和<
,必须用引号引起来(或者是用反斜杠转义)。否则,它们会被 shell 解释为重定向操作符。
下面是一个示例。
#!/bin/bash
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
上面代码中,首先确定$ANSWER
字符串是否为空。如果为空,就终止脚本,并把退出状态设为1
。
注意,这里的echo
命令把错误信息There is no answer.
重定向到标准错误,这是处理错误信息的常用方法。如果$ANSWER
字符串不为空,就判断它的值是否等于yes
、no
或者maybe
。
同时字符串判断时,变量要放在双引号之中,比如[ -n "$COUNT" ]
,否则变量替换成字符串以后,test
命令可能会报错,提示参数过多。