if 结构
语法:
if condition
then
command
command
...
elif condition
then
command
command
...
else
command
command
...
if then
同写一行时,需要用 ;
分隔
if condition; then
command
command
...
elif condition; then
command
command
...
else
command
command
...
也可以只写在一行:
if true; then echo "true"; fi
if true; then echo "true"; else echo "false"; fi
if condition; then fi
true、false、:命令
Bash 中有两个有意思的变量:true、false。这两个命令很简单,就是设置退出码。
# 查看类型
$ type -a true false
true is a shell builtin
true is /usr/bin/true
false is a shell builtin
false is /usr/bin/false
# 测试退出码
$ true
$ echo $?
0
$ false
$ echo $?
1
还有一个更有意思的命令:
,没错就是一个冒号,它的效果等价于 true 命令:
# 查看类型
$ type -a :
: is a shell builtin
# 测试退出码
$ :
$ echo $?
0
判断条件
在一般的编程语言中,条件判断通常是判断一个布尔值(true/false
),或者 0
、1
这样的数据。但是在 Shell 中,判断的数据有所不同:
1、命令的退出码(执行结果):执行command;
后,判断echo $?
输出的退出码值(0:true
,1:false
)。
先执行一遍命令后在判断:
$ if echo "before check"; then echo "true"; fi
before check
true
2、算术运算结果(执行结果):执行(( ... ))
算术运算扩展后,判断echo $?
输出的退出码值(0:true
,1:false
)。
# 不输出
$ if ((0)); then echo "true"; fi
$ if ((2-2)); then echo "true"; fi
# 输出
$ if ((1)); then echo "true"; fi
true
$ if ((1+2)); then echo "true"; fi
true
$ if ((1-2)); then echo "true"; fi
true
注意最后一个测试用例:((1-2))
,也进行了打印。这是因为 Bash 的算术运算扩展中,只要计算结果不为0,退出码就返回0。也就是说,只有计算结果为0的时候,退出码才会返回1。
在总结一下:if 判断中的算术运算扩展的计算结果不为0时才会执行 if 下的代码,即使计算结果为负数。
$ ((1-2))
$ echo $?
0
$ ((1-1))
$ echo $?
1
再看一下进行数值比较时的退出码:
$ ((1>1))
$ echo $?
1
$ ((1>0))
$ echo $?
0
这个就很好理解了,如果逻辑正确则返回0(true),否则返回1(false)。
Shell 的条件判断值还可以写多个,但真正判断时,只取最后一个:
# 因为取 false 命令的退出码,所以最后不会输出"true"
if true;false; then
echo "true";
fi
test 命令
Shell 内置命令,用来检测某个条件是否成立,成立返回0,否则返回1。通常和 if 语句一起使用,并且大部分 if 语句都依赖 test。
test 更常用于BASH shell 脚本中,作为控制逻辑和程序流程 的条件语句的一部分。
# 写法一
test expression
# 写法二
[ expression ]
# 写法三
[[ expression ]]
上面的三种写法等价,但是第三种可以支持正则。并且第二种和三种写法,[
和]
与内部的表达式之间必须有空格,否则无法执行:
# -e 参数:判断文件是否存在
$ test -e /etc/profile
$ echo $?
0
$ [ -e /etc/profile2 ]
$ echo $?
1
$ [ -e /etc/profile]
-bash: [: missing `]'
结合if
,汇总一下写法:
# 写法一
if test -e /etc/profile ; then
echo "Found profile file"
fi
# 写法二
if [ -e /etc/profile ] ; then
echo "Found profile file"
fi
# 写法三
if [[ -e /etc/profile ]] ; then
echo "Found profile file"
fi
test 命令详解
文件判断
[ -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(同 L)。[ -k file ]
:如果 file 存在并且设置了它的“sticky bit”,则为true。[ -L file ]
:如果 file 存在并且是一个符号链接,则为true(同 h)。[ -N file ]
:如果 file 存在并且自上次读取后已被修改,则为true。[ -O file ]
:如果 file 存在并且属于当前用户,则为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
字符串判断
[ 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
整数判断
[ integer1 -eq integer2 ]
:如果integer1等于integer2,则为true。[ integer1 -ne integer2 ]
:如果integer1不等于integer2,则为true。[ integer1 -le integer2 ]
:如果integer1小于或等于integer2,则为true。[ integer1 -lt integer2 ]
:如果integer1小于integer2,则为true。[ integer1 -ge integer2 ]
:如果integer1大于或等于integer2,则为true。[ integer1 -gt integer2 ]
:如果integer1大于integer2,则为true。
栗子:
#!/bin/bash
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
正则判断
语法:[[ string1 =~ regex ]]
=~
是正则比较运算符,后面跟着正则表达式 regex
栗子:
#!/bin/bash
INT=-5
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
echo "INT is an integer."
exit 0
else
echo "INT is not an integer." >&2
exit 1
fi
test 判断的逻辑运算
- AND(逻辑与):
&&
- OR(逻辑或):
||
- NOT(非):
!
栗子:
#!/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."
exit 1
fi
普通命令的逻辑运算
如果if
不使用test
命令,而是普通命令(实际test
也是一种命令),可以根据命令的退出码进行逻辑运算。
- AND(逻辑与):
&&
- OR(逻辑或):
||
栗子:
#!/bin/bash
DICTIONARY=/home/shellTest
FIND_FILE=${DICTIONARY}/if.sh
# 判断目录下的指定文件是否为普通文件
if [ -e "${DICTIONARY}" ] && find "${FIND_FILE}" && [ -f "${FIND_FILE}" ]; then
echo "Found regular file if.sh"
else
echo "Cannot find regular file if.sh"
fi
if 小结
无论是 test 命令、普通命令、算术运算扩展,if 语句实际只会看它们的退出码。退出码是共性的,所以又可以根据此混合执行、进行逻辑运算执行。
case 结构
switch case
结构,用于多值判断。跟包含多个 elif 的 if 结构等价。
语法:
case expression in
case_pattern1 )
commands ;;
case_pattern2 )
commands ;;
...
esac
expression
是一个表达式,case_pattern
是表达式的值或者一个模式,可以有多个case_pattern
,用来匹配多个值,每个case_pattern
以两个分号;
表示结尾。
例子:
#!/bin/bash
OS=$(uname -s)
case "$OS" in
FreeBSD) echo "This is FreeBSD" ;;
Darwin) echo "This is Mac OSX" ;;
AIX) echo "This is AIX" ;;
Minix) echo "This is Minix" ;;
Linux) echo "This is Linux" ;;
*) echo "Failed to identify this OS" ;;
esac
case 的匹配模式可以使用各种通配符:
#!/bin/bash
echo -n "输入一个字母或数字 > "
# read 命令会在后面的 Blog 中介绍
# 理解为读取用户输入数据即可
read character
case $character in
[[:lower:]] | [[:upper:]] ) echo "输入了字母 $character"
;;
[0-9] ) echo "输入了数字 $character"
;;
* ) echo "输入不符合要求"
esac
如果想要当匹配一个值或模式后,继续匹配其他的值或者模式,可以修改表示 case 处理结束的 ;;
为 ;;&
。例子:
#!/bin/bash
OS=$(uname -s)
case "$OS" in
Linux) echo "This is Linux" ;;&
*) echo "Failed to identify this OS" ;;&
esac
执行后输出:
This is Linux
Failed to identify this OS
但是若匹配成功的一个值或模式,以 ;;
结尾,就会直接结束匹配,即使其他的 case 以 ;;&
结尾。