文章目录


让我们来考虑一个使用伪代码表示地简单逻辑示例。伪代码是计算机语言的一种模拟,为的是方便人们理解。

X = 5
If X = 5, then:
Say "X equal 5."
Otherwise:
Say "X is not equal to 5."

这就是一个分支的例子。根据条件,如果“X=5”,则表示为“X等于5”;否则,则说“X不等于5”。

一、使用if

通过shell,我们可以对上面地逻辑进行编码,如下所示:

x=5
if [ $x = 5 ]; then
echo "x equals 5."
else
echo "x does not equal 5."
fi

if语句地语法格式如下

if commands; then
commands
elif commands; then
commands
else
commands
fi

二、退出状态

命令(包括我们编写地脚本和shell函数)在执行完毕后,会向操作系统发送一个值,称之为“退出状态”。这个值是0~255的整数,用来指示命令执行成功还是失败。按照惯例,数值0表示执行成功,其他地数值表示执行失败。shell提供了一个可以用来检测退出状态地参数。

编写shell脚本--流控制:IF分支语句_字符串


在这个例子中,我们两次执行了​​ls​​​命令。第一次,命令执行成功,如果显示参数“​​$?​​​”的值,可以看到它是0。第二次执行​​ls​​​命令时,产生了一个错误,再次显示参数“​​$?​​”地值,这次则为2,表示这个命令遇到了一个错误。有些命令使用不同的退出值来诊断错误,而许多命令在执行失败时,只是简单地退出并发送数字1。man手册中经常会包括一个标题为“Exit Status”的段落,它描述使用地代码。数字0总是表示执行成功。

shell提供了两个非常简单地内置命令,它们不做任何事情,除了以一个0或1退出状态来终止执行。“​​true​​​”命令总是表示执行成功,而“​​false​​”命令总是表示执行失败。

编写shell脚本--流控制:IF分支语句_运算符_02


我们可以用这两个命令来查看if语句是如果工作地。if语句真正做地事情是评估命令地成功和失败。

编写shell脚本--流控制:IF分支语句_字符串_03


如果if后面有一系列地命令,那么则根据最后一个命令地执行结果进行评估。

编写shell脚本--流控制:IF分支语句_字符串_04

三、使用test命令

目前为止,经常和if一起使用地命令是​​test​​​。​​test​​命令会执行各种检查和比较。这个命令有两种等价地形式:

test

以及更流行的:

[ expression ]

这里的expression是一个表达式,其结果是true或false。当这个表达式为true时,​​test​​​命令返回一个零退出状态,当这个表达式为false时,​​test​​命令的退出状态为1。

3.1、文件表达式

测试文件的表达式

表达式

成为true的条件

file1 -ef file2

file1和file2拥有相同的信息节点编号(这两个文件通过硬链接指向同一个文件)

file1 -nt file2

file1比file2新

file1 -ot file2

fil1比file2旧

-b file

file存在并且时一个块(设备)文件

-c file

file存在并且时一个字符(设备)文件

-d file

file存在并且是一个目录

-e file

file存在

-f file

file存在并且是一个普通文件

-g file

file存在并且设置了组ID

-G file

file存在并且属于有效组ID

-k file

file存在并且有“粘滞位(sticky bit)”属性

-L file

file存在并且是一个符号链接

-O file

file存在并且属于有效用户ID

-p file

file存在并且是一个命令管道

-r file

file存在并且可读(有效用户有可读权限)

-s file

file存在并且其长度大于0

-S file

file存在并且是一个网络套接字

-t fd

fd是一个定向到终端/从终端定向的文件描述符,可以用来确定标准输入/输出/错误是否被重定向

-u file

file存在并且设置了setuid位

-w file

file存在并且可写(有效用户拥有可写权限)

-x file

file存在并且可执行(有效用户拥有可执行/搜索权限)

下面的脚本可用来演示某些文件表达式;

#!/bin/bash

#test-file: Evaluate the status of a file

FILE=~/.bashrc
if [ -e "$FILE" ]; then
if [ -f "$FILE" ]; then
echo "$FILE
fi
if [ -d "$FILE" ]; then
echo "$FILE
fi
if [ -r "$FILE" ]; then
echo "$FILE
fi
if [ -w "$FILE" ]; then
echo "$FILE
fi
if [ -x "$FILE" ]; then
echo "$FILE
fi
else
echo "$FILE
exit 1
fi

exit

这个脚本会评估赋值给常量FILE的文件,并显示评估结果。关于该脚本,需要注意两个有趣的地方。首先,要注意​​$FILE​​​在表达式内是怎么被引用的。尽管引号不是必需的,但是这可以防范参数为空的情况。如果​​$FILE​​​的参数扩展产生一个空值,将导致一个错误(操作符会被解释为非空的字符串,而不是操作符)。用引号把参数括起来可以确保操作符后面总是跟随着一个字符串,即使字符串为空。其次,注意脚本末尾的​​exit​​​命令。这个​​exit​​​命令接受一个单独的可选参数,它敬爱嗯成为脚本的退出状态。当不传递参数时,退出状态默认为0。以这种方法使用​​exit​​​命令,当​​$FILE​​​扩展为一个不存在的文件名时,可以允许脚本提示失败。这个​​exit​​​命令出现在脚本的最后一行。这样,当脚本执行到最后时,不管怎么样,默认情况下它将以退出状态零终止。
类似的,通过在return命令中包含一个整数参数,shell函数可以返回一个退出状态。如果要将上面的脚本转换为一个shell函数,从而能够在一个更大的程序中使用,可以将exit命令替换为​​​return​​命令,并得到想要的行为。

test_file () {
FILE=~/.bashrc
if [ -e "$FILE" ]; then
if [ -f "$FILE" ]; then
echo "$FILE
fi
if [ -d "$FILE" ]; then
echo "$FILE
fi
if [ -r "$FILE" ]; then
echo "$FILE
fi
if [ -w "$FILE" ]; then
echo "$FILE
fi
if [ -x "$FILE" ]; then
echo "$FILE
fi
else
echo "$FILE
return 1
fi

)

3.2、字符串表达式

测试字符串表达式

表达式

成为true的条件

string

string不为i空

-n string

string的长度大于0

-z string

string的长度等于0

string1=string2

string1和string2相等。单等号和双等号都可以使用,但是双等好使用的更多

string2==string1

string1!=string2

string1和string2不相等

string1>string2

在排序时,string1在string2之后

string1<string2

在排序时,string1在string2之前

警告在使用test命令时,">“和”<"运算符必须用引号括起来(或者时使用反斜杠进行转义)。如果不这样做,就会被shell解释为重定向操作符,从而造成潜在的破坏性结果。同时注意,尽管bash文档中已经声明,排序遵从当前语系的排列规则,但并非如此。在bash 4.0版本以前(包括4.0版本),使用的是ASCLL(POSIX)排序方式。

下面是一个合并字符串表法式的脚本。

#!/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

在这个脚本中,我们评估了常量ANSWER。我们首先检查了这个字符串是否是空。如果是空,则终止脚本,并且把退出状态设置为1。注意应用到​​echo​​命令的重定向操作符i。它把错误信息"There is no answer."重定向到标准错误,这也是处理错误信息的“合理”方法。如果字符串非空,则评估这个字符串的值是否是“yes”、“no”或者“maybe”。我们使用elif(else if的缩写)来实现上述目的。通过使用elif,我们可以建立一个更复杂的逻辑测试。

3.3、整数表达式

整数判断操作

表达式

成为true的条件

integer1 -eq integer2

integer1和integer2相等

integer1 -ne integer2

integer1和integer2不想等

integer1 -le integer2

integer1小于等于integer2

integer1 -lt integer2

integer1小于integer2

integer1 -ge integer1

integer1大于等于integer2

integer1 -gt integer2

integer1大于integer2

下面是一个演示这些表达式的脚本。

#!/bin/bash

#test-integer: evaluate the value of a 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

这个脚本最有意思的地方是,如何判断一个整数是奇数还是偶数。通过用模数2进行求模运算,也就是将这个数值除以2并且返回余数,这就可以知道这个数值是奇数还是偶数了。

四、更现代地test命令版本

bash的最近版本包括了一个符号命令,它相当于增强的​​test​​命令。下面是这个命令的语法。

[ [expression ] ]

expression是一个表达式,其结果为true或false。[ [ ] ]命令和​​test​​命令类似(支持所有的表达式),不过增加了一个很重要的新字符串表达式。

string1=~regex

如果string1与扩展的正则表达式匹配,则返回true。这就为执行数据验证这样的任务提供了许多可能性。在前面整数表达式的例子中,如果常量INT含有整数意外的其他值,脚本就会执行失败。脚本需要有一种方法来验证常量包含的是否是整数。可以使用 [ [ ] ]和=~字符传表达式操作符,按照如下方式改进脚本:

#!/bin/bash

#test-integer: evaluate the value of a 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
exit1
fi

通过应用正则表达式,我们可以限制INT的值只能是字符串,而且字符串必须以减号(可选)开头,后面跟着一个或者多个数字。这个表达式同样消除了INT为空值的可能性。
[ [ ] ]增加的另外一个特性是==操作符支持模式匹配,就像路径名扩展那样。举例如下:

$ FILE = foo.bar
$ if [[ $FILE == foo.* ]]; then
> echo "$FILE
> fi
foo.bar matches pattern 'foo.*'

这使得[[ ]]在评估文件和路径名的时候非常有用。

五、(())–为整数设计

除了[[]]的复合命令之外,bash同样提供了(())复合命令,它可用于操作整数。该命令支持一套完整的算术计算。

(())用于执行算术真值测试(arithmetic truth test)。当算术计算的结果是非零值时,则算术真值测试为true。

编写shell脚本--流控制:IF分支语句_bash_05


使用(()),我们可以简化test-integer2脚本,如下所示。

#!/bin/bash

#test-integer2a: evaluate the value of a 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
exit1
fi

注意:这里使用了小于号、大于号和等号用来测试相等性。在处理整数时,这些语法看起来也更自然。另外,由于(())复合命令只是shell语法的一部分,而非普通命令,并且只能处理整数,所以它能够通过名字来识别变量,而且不需要执行扩展操作。

六、组合表达式

我们也可以将表达式组合起来,来创建更复杂的计算。表达式是使用逻辑运算符组合起来的。与​​test​​​和​​[[]]​​命令配套的逻辑运算符有所三个,它们是AND、OR和NOT。test和[[]]使用不同的操作符来表示这三种逻辑操作,如下表所示。

Operation

test

[[]]and(())

AND

-a

&&

OR

-o

NOT

!

!

下面是一个AND运算的例子。这个脚本用来检测一个整数是否属于某个范围内的值。

#!/bin/bash

#test-integer3:determine if an integer is within a

MIN_VAL=1
MAX_VAL=100

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
fi
else
echo "$INT >&2
exit 1
fi

这个脚本中,检测整数INT的值是否在MIN_VAL和MAX_VAL之间。这是通过一个[[]]运算符来执行的,该运算符中包含两个用“&&”运算符分隔来的表达式。当然我们也可以使用test完成该功能。

if [ $INT -ge $MIN_VAL -a $INT -le $MAX_VAL ]; then
echo "$INT is within $MIN_VAL to $MAX_VAL."
else
echo "$INT
fi

"!"否定运算符对表达式的运算结果取反,如果表达式为false,则返回true;反之,如果表达式为true,则返回false。在下面的脚本中,我们将修改判断逻辑,以判断INT的值是否在指定的范围之外:

#!/bin/bash

#test-integer3:determine if an integer is within a

MIN_VAL=1
MAX_VAL=100

if [[ "$INT" = ~ ^-?[0-9]+$ ]]; then
if [[ !(INT -ge MIN_VAL && INT -le MAX_VAL) ]]; then
echo "$INT is outside $MIN_VAL to $MAX_VAL."
else
echo "$INT
fi
else
echo "$INT >&2
exit 1
fi

当然我们也可以用圆括号把表达式括起来,以进行分组。如果不是用圆括号,“!”运算符仅对第一个表达式有效,而不是对两个组合后的表达式有效。用test命令可以按照如下进行编码:

if [ ! \($INT -ge $MIN_VAL -a $INT -le $MAX_VAL\) ]; then
echo "$INT is outside $MIN_VAL to $MAX_VAL."
else
echo "$INT
fi

由于​​test​​​使用的所有表达式和操作符都被shell看做命令参数(不像​​[[]]​​​以及​​(())​​​),因此在bash中有特殊含义的字符,如"<"、">"、"(“和”)",必须用引号括起来或者进行转义。
​​​test​​​和​​[[]]​​命令基本上完成相同的功能,那么,哪一个更好呢?test更为传统(而且也是POSIX的一部分),而[[]]则是bash特定的。由于test命令的使用更为广泛,因此知道如何使用test更为重要。但是[[]]命令显然更有用,而且更容易编码。

七、控制运算符:另一种方式的分支

bash还提供了两种可以执行分支的控制运算符。“&&”(AND)和“||”(OR)运算符与[[]]复合命令中的逻辑运算符类似。语法如下:

command1 &&

command1 ||

理解这两个运算符是非常重要的。对于“&&”运算符来说,先执行command1,只有在command1执行成功时,command2才能够执行。对于“||”运算符来说,先执行command1,则只有在command1执行失败时,command2才能够执行。
从实用性考虑,这意味着可以这样做:

$ mkdir temp && cd temp

这会创建一个temp目录,并且当这个创建工作执行成功后,当前的工作目录此阿会更改为temp。只有在第一个​​mkdir​​命令执行成功后,才会尝试执行第二个命令。同样,看如下命令:

$ [-d temp] || mkdir temp

这个命令先检测temp目录是否存在,只有当检测失败时,才会创建这个目录。这个构造类型可以轻松处理脚本中的错误,例如:

$ [-d temp] || exit 1

如果脚本需要temp目录,而这个目录不存在,则这个脚本会终止,并且退出状态为1。