流程控制：if 分支结构

In the last chapter, we were presented with a problem. How can we make our report generator script adapt to the privileges of the user running the script? The solution to this problem will require us to find a way to “change directions” within our script, based on a the results of a test. In programming terms, we need the program to branch. Let’s consider a simple example of logic expressed in pseudocode, a simulation of a computer language intended for human consumption:

X=5If X = 5, then:Say “X equals 5.”Otherwise:Say “X is not equal to 5.”

This is an example of a branch. Based on the condition, “Does X = 5?” do one thing, “Say X equals 5,” otherwise do another thing, “Say X is not equal to 5.”

if

Using the shell, we can code the logic above as follows:

x=5if [ $x = 5 ]; then echo "x equals 5."else echo "x does not equal 5."fi or we can enter it directly at the command line (slightly shortened): 或者我们可以直接在命令行中输入以上代码（略有缩短）： [me@linuxbox ~]$ x=5[me@linuxbox ~]$if [$x = 5 ]; then echo "equals 5"; else echo "doesnot equal 5"; fiequals 5[me@linuxbox ~]$x=0[me@linuxbox ~]$ if [ $x = 5 ]; then echo "equals 5"; else echo "doesnot equal 5"; fidoes not equal 5 In this example, we execute the command twice. Once, with the value of x set to 5, which results in the string “equals 5” being output, and the second time with the value of x set to 0, which results in the string “does not equal 5” being output. 在这个例子中，我们执行了两次这个命令。第一次是，把 x 的值设置为5，从而导致输出字符串“equals 5”, 第二次是，把 x 的值设置为0，从而导致输出字符串“does not equal 5”。 The if statement has the following syntax: 这个 if 语句语法如下： if commands; then commands[elif commands; then commands...][else commands]fi where commands is a list of commands. This is a little confusing at first glance. But before we can clear this up, we have to look at how the shell evaluates the success or failure of a command. 这里的 commands 是指一系列命令。第一眼看到会有点儿困惑。但是在我们弄清楚这些语句之前，我们 必须看一下 shell 是如何评判一个命令的成功与失败的。 退出状态 Commands (including the scripts and shell functions we write) issue a value to the system when they terminate, called an exit status. This value, which is an integer in the range of 0 to 255, indicates the success or failure of the command’s execution. By convention, a value of zero indicates success and any other value indicates failure. The shell provides a parameter that we can use to examine the exit status. Here we see it in action: 当命令执行完毕后，命令（包括我们编写的脚本和 shell 函数）会给系统发送一个值，叫做退出状态。 这个值是一个 0 到 255 之间的整数，说明命令执行成功或是失败。按照惯例，一个零值说明成功，其它所有值说明失败。 Shell 提供了一个参数，我们可以用它检查退出状态。用具体实例看一下： [me@linuxbox ~]$ ls -d /usr/bin/usr/bin[me@linuxbox ~]$echo$?0[me@linuxbox ~]$ls -d /bin/usrls: cannot access /bin/usr: No such file or directory[me@linuxbox ~]$ echo $?2 In this example, we execute the ls command twice. The first time, the command executes successfully. If we display the value of the parameter $?, we see that it is zero. We execute the ls command a second time, producing an error and examine the parameter $? again. This time it contains a 2, indicating that the command encountered an error. Some commands use different exit status values to provide diagnostics for errors, while many commands simply exit with a value of one when they fail. Man pages often include a section entitled “Exit Status,” describing what codes are used. However, a zero always indicates success. 在这个例子中，我们执行了两次 ls 命令。第一次，命令执行成功。如果我们显示参数$?的值，我们 看到它是零。我们第二次执行 ls 命令的时候，产生了一个错误，并再次查看参数$?。这次它包含一个 数字 2，表明这个命令遇到了一个错误。有些命令使用不同的退出值，来诊断错误，而许多命令当 它们执行失败的时候，会简单地退出并发送一个数字1。手册页中经常会包含一章标题为“退出状态”的内容， 描述了使用的代码。然而，一个零总是表明成功。 The shell provides two extremely simple builtin commands that do nothing except terminate with either a zero or one exit status. The true command always executes successfully and the false command always executes unsuccessfully: shell 提供了两个极其简单的内部命令，它们不做任何事情，除了以一个0或1退出状态来终止执行。 True 命令总是执行成功，而 false 命令总是执行失败： [me@linuxbox~]$ true[me@linuxbox~]$echo$?0[me@linuxbox~]$false[me@linuxbox~]$ echo $?1 We can use these commands to see how the if statement works. What the if statement really does is evaluate the success or failure of commands: 我们能够使用这些命令，来看一下 if 语句是怎样工作的。If 语句真正做的事情是计算命令执行成功或失败： [me@linuxbox ~]$ if true; then echo "It's true."; fiIt's true.[me@linuxbox ~]$if false; then echo "It's true."; fi[me@linuxbox ~]$

The command echo “It’s true.” is executed when the command following if executes successfully, and is not executed when the command following if does not execute successfully. If a list of commands follows if, the last command in the list is evaluated:

[me@linuxbox ~]$if false; true; then echo "It's true."; fiIt's true.[me@linuxbox ~]$ if true; false; then echo "It's true."; fi[me@linuxbox ~]$ 测试 By far, the command used most frequently with if is test. The test command performs a variety of checks and comparisons. It has two equivalent forms: 到目前为止，经常与 if 一块使用的命令是 test。这个 test 命令执行各种各样的检查与比较。 它有两种等价模式： test expression and the more popular: 比较流行的格式是： [ expression ] where expression is an expression that is evaluated as either true or false. The test command returns an exit status of zero when the expression is true and a status of one when the expression is false. 这里的 expression 是一个表达式，其执行结果是 true 或者是 false。当表达式为真时，这个 test 命令返回一个零 退出状态，当表达式为假时，test 命令退出状态为1。 文件表达式 The following expressions are used to evaluate the status of files: 以下表达式被用来计算文件状态： Table 28-1: test File Expressions Expression Is Ture If file1 -ef file2 file1 and file2 have the same inode numbers (the two filenames refer to the same file by hard linking). file1 -nt file2 file 1 is newer than file2. file1 -ot file2 file1 is older than file2. -b file file exists and is a block special (device) file. -c file file exists and is a character special (device) file. -d file file exists and is a directory. -e file file exists. -f file file exists and is a regular file. -g file file exists and is set-group-ID. -G file file exists and is owned by the effective group ID. -k file file exists and has its “sticky bit” set. -L file file exists and is a symbolic link. -O file file exists and is owned by the effective user ID. -p file file exists and is a named pipe. -r file file exists and is readable (has readable permission for the effective user). -s file file exists and has a length greater than zero. -S file file exists and is a network socket. -t fd fd is a file descriptor directed to/from the terminal. This can be used to determine whether standard input/output/ error is being redirected. -u file file exists and is setuid. -w file file exists and is writable (has write permission for the effective user). -x file file exists and is executable (has execute/search permission for the effective user). 表28-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 存在并且是一个普通文件。 -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 存在且其长度大于零。 -S file file 存在且是一个网络 socket。 -t fd fd 是一个定向到终端／从终端定向的文件描述符 。 这可以被用来决定是否重定向了标准输入／输出错误。 -u file file 存在并且设置了 setuid 位。 -w file file 存在并且可写（有效用户拥有可写权限）。 -x file file 存在并且可执行（有效用户有执行／搜索权限）。 Here we have a script that demonstrates some of the file expressions: 这里我们有一个脚本说明了一些文件表达式： #!/bin/bash# test-file: Evaluate the status of a fileFILE=~/.bashrcif [ -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."    fielse    echo "$FILE does not exist" exit 1fiexit The script evaluates the file assigned to the constant FILE and displays its results as the evaluation is performed. There are two interesting things to note about this script. First, notice how the parameter $FILE is quoted within the expressions. This is not required, but is a defense against the parameter being empty. If the parameter expansion of $FILE were to result in an empty value, it would cause an error (the operators would be interpreted as non-null strings rather than operators). Using the quotes around the parameter insures that the operator is always followed by a string, even if the string is empty. Second, notice the presence of the exit commands near the end of the script. The exit command accepts a single, optional argument, which becomes the script’s exit status. When no argument is passed, the exit status defaults to zero. Using exit in this way allows the script to indicate failure if $FILE expands to the name of a nonexistent file. The exit command appearing on the last line of the script is there as a formality. When a script “runs off the end” (reaches end of file), it terminates with an exit status of zero by default, anyway.

By applying the regular expression, we are able to limit the value of INT to only strings that begin with an optional minus sign, followed by one or more numerals. This expression also eliminates the possibility of empty values.

Another added feature of [[ ]] is that the == operator supports pattern matching the same way pathname expansion does. For example:

[[ ]]添加的另一个功能是==操作符支持类型匹配，正如路径名展开所做的那样。例如：

[me@linuxbox ~]$FILE=foo.bar[me@linuxbox ~]$ if [[ $FILE == foo.* ]]; then> echo "$FILE matches pattern 'foo.*'"> fifoo.bar matches pattern 'foo.*'

This makes [[ ]] useful for evaluating file and path names.

(( )) - 为整数设计

In addition to the [[ ]] compound command, bash also provides the (( )) compound command, which is useful for operating on integers. It supports a full set of arithmetic evaluations, a subject we will cover fully in Chapter 35.

(( )) is used to perform arithmetic truth tests. An arithmetic truth test results in true if the result of the arithmetic evaluation is non-zero.

(( ))被用来执行算术真测试。如果算术计算的结果是非零值，则其测试值为真。

[me@linuxbox ~]$if ((1)); then echo "It is true."; fiIt is true.[me@linuxbox ~]$ if ((0)); then echo "It is true."; fi[me@linuxbox ~]$ Using (( )), we can slightly simplify the test-integer2 script like this: 使用(( ))，我们能够略微简化 test-integer2脚本，像这样： #!/bin/bash# test-integer2a: evaluate the value of an integer.INT=-5if [[ "$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 fielse echo "INT is not an integer." >&2 exit 1fi Notice that we use less than and greater than signs and that == is used to test for equivalence. This is a more natural looking syntax for working with integers. Notice too, that because the compound command (( )) is part of the shell syntax rather than an ordinary command, and it deals only with integers, it is able to recognize variables by name and does not require expansion to be performed. We’ll discuss (( )) and the related arithmetic expansion further in Chapter 35. 注意我们使用小于和大于符号，以及==用来测试是否相等。这是使用整数较为自然的语法了。也要 注意，因为复合命令 (( )) 是 shell 语法的一部分，而不是一个普通的命令，而且它只处理整数， 所以它能够通过名字识别出变量，而不需要执行展开操作。我们将在第35中进一步讨论 (( )) 命令 和相关的算术展开操作。 结合表达式 It’s also possible to combine expressions to create more complex evaluations. Expressions are combined by using logical operators. We saw these in Chapter 18, when we learned about the find command. There are three logical operations for test and [[ ]]. They are AND, OR and NOT. test and [[ ]] use different operators to represent these operations : 也有可能把表达式结合起来创建更复杂的计算。通过使用逻辑操作符来结合表达式。我们 在第18章中学习 find 命令的时候已经知道了这些。有三个用于 test 和 [[ ]] 的逻辑操作。 它们是 AND、OR 和 NOT。test 和 [[ ]] 使用不同的操作符来表示这些操作： Table 28-4: Logical Operators Operation test [[ ]] and (( )) AND -a && OR -o || NOT ! ! 表28-4: 逻辑操作符 操作符 测试 [[ ]] and (( )) AND -a && OR -o || NOT ! ! Here’s an example of an AND operation. The following script determines if an integer is within a range of values: 这里有一个 AND 操作的示例。下面的脚本决定了一个整数是否属于某个范围内的值： #!/bin/bash# test-integer3: determine if an integer is within a# specified range of values.MIN_VAL=1MAX_VAL=100INT=50if [[ "$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." fielse echo "INT is not an integer." >&2 exit 1fi We also include parentheses around the expression, for grouping. If these were not included, the negation would only apply to the first expression and not the combination of the two. Coding this with test would be done this way: 我们也可以对表达式使用圆括号，为的是分组。如果不使用括号，那么否定只应用于第一个 表达式，而不是两个组合的表达式。用 test 可以这样来编码： 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 Since all expressions and operators used by test are treated as command arguments by the shell (unlike [[ ]] and (( )) ), characters which have special meaning to bash, such as <, >, (, and ), must be quoted or escaped. 因为 test 使用的所有的表达式和操作符都被 shell 看作是命令参数（不像 [[ ]](( )) ）， 对于 bash 有特殊含义的字符，比如说 <，>，(，和 )，必须引起来或者是转义。 Seeing that test and [[ ]] do roughly the same thing, which is preferable? test is traditional (and part of POSIX), whereas [[ ]] is specific to bash. It’s important to know how to use test, since it is very widely used, but [[ ]] is clearly more useful and is easier to code. 知道了 test 和 [[ ]] 基本上完成相同的事情，哪一个更好呢？test 更传统（是 POSIX 的一部分）， 然而 [[ ]] 特定于 bash。知道怎样使用 test 很重要，因为它被非常广泛地应用，但是显然 [[ ]] 更 有用，并更易于编码。 Portability Is The Hobgoblin Of Little Minds 可移植性是头脑狭隘人士的心魔 If you talk to “real” Unix people, you quickly discover that many of them don’t like Linux very much. They regard it as impure and unclean. One tenet of Unix followers is that everything should be “portable.” This means that any script you write should be able to run, unchanged, on any Unix-like system. 如果你和“真正的”Unix 用户交谈，你很快就会发现他们大多数人不是非常喜欢 Linux。他们 认为 Linux 肮脏且不干净。Unix 追随者的一个宗旨是，一切都应“可移植的”。这意味着你编写 的任意一个脚本都应当无需修改，就能运行在任何一个类 Unix 的系统中。 Unix people have good reason to believe this. Having seen what proprietary extensions to commands and shells did to the Unix world before POSIX, they are naturally wary of the effect of Linux on their beloved OS. Unix 用户有充分的理由相信这一点。在 POSIX 之前，Unix 用户已经看到了命令的专有扩展以及 shell 对 Unix 世界的所做所为，他们自然会警惕 Linux 对他们心爱系统的影响。 But portability has a serious downside. It prevents progress. It requires that things are always done using “lowest common denominator” techniques. In the case of shell programming, it means making everything compatible with sh, the original Bourne shell. 但是可移植性有一个严重的缺点。它防碍了进步。它要求做事情要遵循“最低常见标准”。 在 shell 编程这种情况下，它意味着一切要与 sh 兼容，最初的 Bourne shell。 This downside is the excuse that proprietary vendors use to justify their proprietary extensions, only they call them “innovations.” But they are really just lock-in devices for their customers. 这个缺点是一个专有软件供应商用来为他们专有的扩展做辩解的借口，只有他们称他们为“创新”。 但是他们只是为他们的客户锁定设备。 The GNU tools, such as bash, have no such restrictions. They encourage portability by supporting standards and by being universally available. You can install bash and the other GNU tools on almost any kind of system, even Windows, without cost. So feel free to use all the features of bash. It’s really portable. GNU 工具，比如说 bash，就没有这些限制。他们通过支持标准和普遍地可用性来鼓励可移植性。你几乎可以 在所有类型的系统中安装 bash 和其它的 GNU 工具，甚至是 Windows，而没有损失。所以就 感觉可以自由的使用 bash 的所有功能。它是真正的可移植。 控制操作符：分支的另一种方法 bash provides two control operators that can perform branching. The && (AND) and || (OR) operators work like the logical operators in the [[ ]] compound command. This is the syntax: bash 支持两种可以执行分支任务的控制操作符。 &&（AND）||（OR）操作符作用如同 复合命令[[ ]]中的逻辑操作符。这是语法： command1 && command2 and command1 || command2 It is important to understand the behavior of these. With the && operator, command1 is executed and command2 is executed if, and only if, command1 is successful. With the || operator, command1 is executed and command2 is executed if, and only if, command1 is unsuccessful. 理解这些操作很重要。对于 && 操作符，先执行 command1，如果并且只有如果 command1 执行成功后， 才会执行 command2。对于 || 操作符，先执行 command1，如果并且只有如果 command1 执行失败后， 才会执行 command2。 In practical terms, it means that we can do something like this: 在实际中，它意味着我们可以做这样的事情： [me@linuxbox ~]$ mkdir temp && cd temp

This will create a directory named temp, and if it succeeds, the current working directory will be changed to temp. The second command is attempted only if the mkdir command is successful. Likewise, a command like this:

[me@linuxbox ~]$[ -d temp ] || mkdir temp will test for the existence of the directory temp, and only if the test fails, will the directory be created. This type of construct is very handy for handling errors in scripts, a subject we will discuss more in later chapters. For example, we could do this in a script: 会测试目录 temp 是否存在，并且只有测试失败之后，才会创建这个目录。这种构造类型非常有助于在 脚本中处理错误，这个主题我们将会在随后的章节中讨论更多。例如，我们在脚本中可以这样做： [ -d temp ] || exit 1 If the script requires the directory temp, and it does not exist, then the script will terminate with an exit status of one. 如果这个脚本要求目录 temp，且目录不存在，然后脚本会终止，并返回退出状态1。 总结 We started this chapter with a question. How could we make our sys_info_page script detect if the user had permission to read all the home directories? With our knowledge of if, we can solve the problem by adding this code to the report_home_space function: 这一章开始于一个问题。我们怎样使 sys_info_page 脚本来检测是否用户拥有权限来读取所有的 家目录？根据我们的 if 知识，我们可以解决这个问题，通过把这些代码添加到 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}

We evaluate the output of the id command. With the -u option, id outputs the numeric user ID number of the effective user. The superuser is always zero and every other user is a number greater than zero. Knowing this, we can construct two different here documents, one taking advantage of superuser privileges, and the other, restricted to the user’s own home directory.

We are going to take a break from the sys_info_page program, but don’t worry. It will be back. In the meantime, we’ll cover some topics that we’ll need when we resume our work.

拓展阅读

There are several sections of the bash man page that provide further detail on the topics covered in this chapter:

bash 手册页中有几部分对本章中涵盖的主题提供了更详细的内容：

• Lists ( 讨论控制操作符 ||&& )

• Compound Commands ( 讨论 [[ ]], (( )) 和 if )

• CONDITIONAL EXPRESSIONS （条件表达式）

• SHELL BUILTIN COMMANDS ( 讨论 test )

Further, the Wikipedia has a good article on the concept of pseudocode: