## # 字符串和数字

Computer programs are all about working with data. In past chapters, we have focused on processing data at the file level. However, many programming problems need to be solved using smaller units of data such as strings and numbers.

In this chapter, we will look at several shell features that are used to manipulate strings and numbers. The shell provides a variety of parameter expansions that perform string operations. In addition to arithmetic expansion (which we touched upon in Chapter 7), there is a common command line program called bc, which performs higher level math.

### # 参数展开

Though parameter expansion came up in Chapter 7, we did not cover it in detail because most parameter expansions are used in scripts rather than on the command line. We have already worked with some forms of parameter expansion; for example, shell variables. The shell provides many more.

#### # 基本参数

The simplest form of parameter expansion is reflected in the ordinary use of variables.

For example:

$a when expanded, becomes whatever the variable a contains. Simple parameters may also be surrounded by braces: 当$a 展开后，会变成变量 a 所包含的值。简单参数也可能用花括号引起来：

${a} This has no effect on the expansion, but is required if the variable is adjacent to other text, which may confuse the shell. In this example, we attempt to create a filename by ap- pending the string “_file” to the contents of the variable a. 虽然这对展开没有影响，但若该变量 a 与其它的文本相邻，可能会把 shell 搞糊涂了。在这个例子中，我们试图 创建一个文件名，通过把字符串 “_file” 附加到变量 a 的值的后面。 [me@linuxbox ~]$ a="foo"
[me@linuxbox ~]$echo "$a_file"


If we perform this sequence, the result will be nothing, because the shell will try to ex- pand a variable named a_file rather than a. This problem can be solved by adding braces:

[me@linuxbox ~]$echo "${a}_file"
foo_file


We have also seen that positional parameters greater than 9 can be accessed by surround- ing the number in braces. For example, to access the eleventh positional parameter, we can do this:

${11} #### # 管理空变量的展开 Several parameter expansions deal with nonexistent and empty variables. These expan- sions are handy for handling missing positional parameters and assigning default values to parameters. 几种用来处理不存在和空变量的参数展开形式。这些展开形式对于解决丢失的位置参数和给参数指定默认值的情况很方便。${parameter:-word}

If parameter is unset (i.e., does not exist) or is empty, this expansion results in the value of word. If parameter is not empty, the expansion results in the value of parameter.

[me@linuxbox ~]$foo= [me@linuxbox ~]$ echo ${foo:-"substitute value if unset"} if unset substitute value [me@linuxbox ~]$ echo $foo [me@linuxbox ~]$ foo=bar
[me@linuxbox ~]$echo${foo:-"substitute value if unset"}
bar
[me@linuxbox ~]$echo$foo
bar


${parameter:=word} If parameter is unset or empty, this expansion results in the value of word. In addition, the value of word is assigned to parameter. If parameter is not empty, the expansion re- sults in the value of parameter. 若 parameter 没有设置或为空，展开结果是 word 的值。另外，word 的值会赋值给 parameter。 若 parameter 不为空，展开结果是 parameter 的值。 [me@linuxbox ~]$ foo=
[me@linuxbox ~]$echo${foo:="default value if unset"}
default value if unset
[me@linuxbox ~]$echo$foo
default value if unset
[me@linuxbox ~]$foo=bar [me@linuxbox ~]$ echo ${foo:="default value if unset"} bar [me@linuxbox ~]$ echo $foo bar  Note: Positional and other special parameters cannot be assigned this way. 注意： 位置参数或其它的特殊参数不能以这种方式赋值。${parameter:?word}

If parameter is unset or empty, this expansion causes the script to exit with an error, and the contents of word are sent to standard error. If parameter is not empty, the expansion results in the value of parameter.

[me@linuxbox ~]$foo= [me@linuxbox ~]$ echo ${foo:?"parameter is empty"} bash: foo: parameter is empty [me@linuxbox ~]$ echo $? 1 [me@linuxbox ~]$ foo=bar
[me@linuxbox ~]$echo${foo:?"parameter is empty"}
bar
[me@linuxbox ~]$echo$?
0


${parameter:+word} If parameter is unset or empty, the expansion results in nothing. If parameter is not empty, the value of word is substituted for parameter; however, the value of parameter is not changed. 若 parameter 没有设置或为空，展开结果为空。若 parameter 不为空， 展开结果是 word 的值会替换掉 parameter 的值；然而，parameter 的值不会改变。 [me@linuxbox ~]$ foo=
[me@linuxbox ~]$echo${foo:+"substitute value if set"}

[me@linuxbox ~]$foo=bar [me@linuxbox ~]$ echo ${foo:+"substitute value if set"} substitute value if set  ### # 返回变量名的参数展开 The shell has the ability to return the names of variables. This is used in some rather exotic situations. shell 具有返回变量名的能力。这会用在一些相当独特的情况下。${!prefix*}

${!prefix@} This expansion returns the names of existing variables with names beginning with prefix. According to the bash documentation, both forms of the expansion perform identically. Here, we list all the variables in the environment with names that begin with BASH: 这种展开会返回以 prefix 开头的已有变量名。根据 bash 文档，这两种展开形式的执行结果相同。 这里，我们列出了所有以 BASH 开头的环境变量名： [me@linuxbox ~]$ echo ${!BASH*} BASH BASH_ARGC BASH_ARGV BASH_COMMAND BASH_COMPLETION BASH_COMPLETION_DIR BASH_LINENO BASH_SOURCE BASH_SUBSHELL BASH_VERSINFO BASH_VERSION  #### # 字符串展开 There is a large set of expansions that can be used to operate on strings. Many of these expansions are particularly well suited for operations on pathnames. 有大量的展开形式可用于操作字符串。其中许多展开形式尤其适用于路径名的展开。${#parameter}

expands into the length of the string contained by parameter. Normally, parameter is a string; however, if parameter is either @ or *, then the expansion results in the number of positional parameters.

[me@linuxbox ~]$foo="This string is long." [me@linuxbox ~]$ echo "'$foo' is${#foo} characters long."
'This string is long.' is 20 characters long.


${parameter:offset}${parameter:offset:length}

These expansions are used to extract a portion of the string contained in parameter. The extraction begins at offset characters from the beginning of the string and continues until the end of the string, unless the length is specified.

[me@linuxbox ~]$foo="This string is long." [me@linuxbox ~]$ echo ${foo:5} string is long. [me@linuxbox ~]$ echo ${foo:5:6} string  If the value of offset is negative, it is taken to mean it starts from the end of the string rather than the beginning. Note that negative values must be preceded by a space to pre- vent confusion with the${parameter:-word} expansion. length, if present, must not be less than zero.

[me@linuxbox ~]$echo${foo: -5}
long.
[me@linuxbox ~]$echo${foo: -5:2}
lo


${parameter#pattern}${parameter##pattern}

These expansions remove a leading portion of the string contained in parameter defined by pattern. pattern is a wildcard pattern like those used in pathname expansion. The dif- ference in the two forms is that the # form removes the shortest match, while the ## form removes the longest match.

[me@linuxbox ~]$foo=file.txt.zip [me@linuxbox ~]$ echo ${foo#*.} txt.zip [me@linuxbox ~]$ echo ${foo##*.} zip ${parameter%pattern}

${parameter%%pattern} These expansions are the same as the # and ## expansions above, except they remove text from the end of the string contained in parameter rather than from the beginning. 这些展开和上面的 # 和 ## 展开一样，除了它们清除的文本从 parameter 所包含字符串的末尾开始，而不是开头。 [me@linuxbox ~]$ foo=file.txt.zip
[me@linuxbox ~]$echo${foo%.*}
file.txt
[me@linuxbox ~]$echo${foo%%.*}
file


${parameter/pattern/string}${parameter//pattern/string}

${parameter/#pattern/string}${parameter/%pattern/string}

This expansion performs a search-and-replace upon the contents of parameter. If text is found matching wildcard pattern, it is replaced with the contents of string. In the normal form, only the first occurrence of pattern is replaced. In the // form, all occurrences are replaced. The /# form requires that the match occur at the beginning of the string, and the /% form requires the match to occur at the end of the string. /string may be omitted, which causes the text matched by pattern to be deleted.

[me@linuxbox~]$foo=JPG.JPG [me@linuxbox ~]$ echo ${foo/JPG/jpg} jpg.JPG [me@linuxbox~]$ echo ${foo//JPG/jpg} jpg.jpg [me@linuxbox~]$ echo ${foo/#JPG/jpg} jpg.JPG [me@linuxbox~]$ echo ${foo/%JPG/jpg} JPG.jpg  Parameter expansion is a good thing to know. The string manipulation expansions can be used as substitutes for other common commands such as sed and cut. Expansions improve the efficiency of scripts by eliminating the use of external programs. As an example, we will modify the longest-word program discussed in the previous chapter to use the parameter expansion {#j} in place of the command substitution _(echo$j | wc -c)_ and its resulting subshell, like so:

max_word=
max_len=
for j in $(strings$i); do
len=${#j} if (( len > max_len )); then max_len=$len
max_word=$j fi done echo "$i: '$max_word' ($max_len characters)"
fi
shift
done


Next, we will compare the efficiency of the two versions by using the time command:

[me@linuxbox ~]$time longest-word2 dirlist-usr-bin.txt dirlist-usr-bin.txt: 'scrollkeeper-get-extended-content-list' (38 characters) real 0m3.618s user 0m1.544s sys 0m1.768s [me@linuxbox ~]$ time longest-word3 dirlist-usr-bin.txt
dirlist-usr-bin.txt: 'scrollkeeper-get-extended-content-list' (38
characters)
real 0m0.060s
user 0m0.056s
sys 0m0.008s


The original version of the script takes 3.618 seconds to scan the text file, while the new version, using parameter expansion, takes only 0.06 seconds — a very significant improvement.

#### # 大小写转换

Recent versions of bash have support for upper/lowercase conversion of strings. bash has four parameter expansions and two options to the declare command to support it.

So what is case conversion good for? Aside from the obvious aesthetic value, it has an important role in programming. Let's consider the case of a database look-up. Imagine that a user has entered a string into a data input field that we want to look up in a database. It's possible the user will enter the value in all uppercase letters or lowercase letters or a combination of both. We certainly don't want to populate our database with every possible permutation of upper and lower case spellings. What to do?

A common approach to this problem is to normalize the user's input. That is, convert it into a standardized form before we attempt the database look-up. We can do this by converting all of the characters in the user's input to either lower or uppercase and ensure that the database entries are normalized the same way.

The declare command can be used to normalize strings to either upper or lowercase. Using declare, we can force a variable to always contain the desired format no matter what is assigned to it:

#!/bin/bash
# ul-declare: demonstrate case conversion via declare
declare -u upper
declare -l lower
if [[ $1 ]]; then upper="$1"
lower="$1" echo$upper
echo $lower fi  In the above script, we use declare to create two variables, upper and lower. We assign the value of the first command line argument (positional parameter 1) to each of the variables and then display them on the screen: 在上面的脚本中，我们使用 declare 命令来创建两个变量，upper 和 lower。我们把第一个命令行参数的值（位置参数1）赋给 每一个变量，然后把变量值在屏幕上显示出来： [me@linuxbox ~]$ ul-declare aBc
ABC
abc


As we can see, the command line argument ("aBc") has been normalized.

There are four parameter expansions that perform upper/lowercase conversion:

Table 35-1: Case Conversion Parameter Expansions
Format Result
${parameter,,} Expand the value of parameter into all lowercase.${parameter,} Expand the value of parameter changing only the first character to lowercase.
${parameter^^} Expand the value of parameter into all uppercase letters.${parameter^} Expand the value of parameter changing only the first character to uppercase (capitalization).

${parameter,,} 把 parameter 的值全部展开成小写字母。${parameter,} 仅仅把 parameter 的第一个字符展开成小写字母。
${parameter^^} 把 parameter 的值全部转换成大写字母。${parameter^} 仅仅把 parameter 的第一个字符转换成大写字母（首字母大写）。

Here is a script that demonstrates these expansions:

#!/bin/bash
# ul-param - demonstrate case conversion via parameter expansion
if [[ $1 ]]; then echo${1,,}
echo ${1,} echo${1^^}
echo ${1^} fi  Here is the script in action: 这里是脚本运行后的结果： [me@linuxbox ~]$ ul-param aBc
abc
aBc
ABC
ABc


Again, we process the first command line argument and output the four variations supported by the parameter expansions. While this script uses the first positional parameter, parameter my be any string, variable, or string expression.

### # 算术求值和展开

We looked at arithmetic expansion in Chapter 7. It is used to perform various arithmetic operations on integers. Its basic form is:

$((expression))  where expression is a valid arithmetic expression. 这里的 expression 是一个有效的算术表达式。 This is related to the compound command (( )) used for arithmetic evaluation (truth tests) we encountered in Chapter 27. 这个与复合命令 (( )) 有关，此命令用做算术求值（真测试），我们在第27章中遇到过。 In previous chapters, we saw some of the common types of expressions and operators. Here, we will look at a more complete list. 在之前的章节中，我们看到过一些类型的表达式和运算符。这里，我们将看到一个更完整的列表。 #### # 数基 Back in Chapter 9, we got a look at octal (base 8) and hexadecimal (base 16) numbers. In arithmetic expressions, the shell supports integer constants in any base. 回到第9章，我们看过八进制（以8为底）和十六进制（以16为底）的数字。在算术表达式中，shell 支持任意进制的整型常量。 Table 35-2: Specifying Different Number Bases Notation Description number By default, numbers without any notation are treated as decimal (base 10) integers. 0number In arithmetic expressions, numbers with a leading zero are considered octal. 0xnumber Hexadecimal notation base#number number is in base 表 35-2: 指定不同的数基 表示法 描述 number 默认情况下，没有任何表示法的数字被看做是十进制数（以10为底）。 0number 在算术表达式中，以零开头的数字被认为是八进制数。 0xnumber 十六进制表示法 base#number number 以 base 为底 Some examples: 一些例子： [me@linuxbox ~]$ echo $((0xff)) 255 [me@linuxbox ~]$ echo $((2#11111111)) 255  In the examples above, we print the value of the hexadecimal number ff (the largest two-digit number) and the largest eight-digit binary (base 2) number. 在上面的示例中，我们打印出十六进制数 ff（最大的两位数）的值和最大的八位二进制数（以2为底）。 #### # 一元运算符 There are two unary operators, the + and -, which are used to indicate if a number is pos- itive or negative, respectively. For example, -5. 有两个一元运算符，+ 和 -，它们被分别用来表示一个数字是正数还是负数。例如，-5。 #### # 简单算术 The ordinary arithmetic operators are listed in the table below: 下表中列出了普通算术运算符： Table 35-3: Arithmetic Operators Operator Description + Addition - Subtraction * Multiplication / Integer division ** Exponentiation % Modulo (remainder) 表 35-3: 算术运算符 运算符 描述 + - * / 整除 ** 乘方 % 取模（余数） Most of these are self-explanatory, but integer division and modulo require further discussion. 其中大部分运算符是不言自明的，但是整除和取模运算符需要进一步解释一下。 Since the shell’s arithmetic only operates on integers, the results of division are always whole numbers: 因为 shell 算术只操作整型，所以除法运算的结果总是整数： [me@linuxbox ~]$ echo $(( 5 / 2 )) 2  This makes the determination of a remainder in a division operation more important: 这使得确定除法运算的余数更为重要： [me@linuxbox ~]$ echo $(( 5 % 2 )) 1  By using the division and modulo operators, we can determine that 5 divided by 2 results in 2, with a remainder of 1. 通过使用除法和取模运算符，我们能够确定5除以2得数是2，余数是1。 Calculating the remainder is useful in loops. It allows an operation to be performed at specified intervals during the loop's execution. In the example below, we display a line of numbers, highlighting each multiple of 5: 在循环中计算余数是很有用处的。在循环执行期间，它允许某一个操作在指定的间隔内执行。在下面的例子中， 我们显示一行数字，并高亮显示5的倍数： #!/bin/bash # modulo : demonstrate the modulo operator for ((i = 0; i <= 20; i = i + 1)); do remainder=$((i % 5))
if (( remainder == 0 )); then
printf "<%d> " $i else printf "%d "$i
fi
done
printf "\n"


When executed, the results look like this:

[me@linuxbox ~]$modulo <0> 1 2 3 4 <5> 6 7 8 9 <10> 11 12 13 14 <15> 16 17 18 19 <20>  #### # 赋值运算符 Although its uses may not be immediately apparent, arithmetic expressions may perform assignment. We have performed assignment many times, though in a different context. Each time we give a variable a value, we are performing assignment. We can also do it within arithmetic expressions: 尽管它的使用不是那么明显，算术表达式可能执行赋值运算。虽然在不同的上下文中，我们已经执行了许多次赋值运算。 每次我们给变量一个值，我们就执行了一次赋值运算。我们也能在算术表达式中执行赋值运算： [me@linuxbox ~]$ foo=
[me@linuxbox ~]$echo$foo
[me@linuxbox ~]$if (( foo = 5 ));then echo "It is true."; fi It is true. [me@linuxbox ~]$ echo $foo 5  In the example above, we first assign an empty value to the variable foo and verify that it is indeed empty. Next, we perform an if with the compound command (( foo = 5 )). This process does two interesting things: 1) it assigns the value of 5 to the variable foo, and 2) it evaluates to true because foo was assigned a nonzero value. 在上面的例子中，首先我们给变量 foo 赋了一个空值，然后验证 foo 的确为空。下一步，我们执行一个 if 复合命令 (( foo = 5 ))。 这个过程完成两件有意思的事情：1）它把5赋值给变量 foo，2）它计算测试条件为真，因为 foo 的值非零。 Note: It is important to remember the exact meaning of the = in the expression above. A single = performs assignment. foo = 5 says “make foo equal to 5,” while == evaluates equivalence. foo == 5 says “does foo equal 5?” This can be very confusing because the test command accepts a single = for string equiva- lence. This is yet another reason to use the more modern [[ ]] and (( )) com- pound commands in place of test. 注意： 记住上面表达式中 = 符号的真正含义非常重要。单个 = 运算符执行赋值运算。foo = 5 是说“使得 foo 等于5”， 而 == 运算符计算等价性。foo == 5 是说“是否 foo 等于5？”。这会让人感到非常迷惑，因为 test 命令接受单个 = 运算符 来测试字符串等价性。这也是使用更现代的 [[ ]] 和 (( )) 复合命令来代替 test 命令的另一个原因。 In addition to the =, the shell also provides notations that perform some very useful as- signments: 除了 = 运算符，shell 也提供了其它一些表示法，来执行一些非常有用的赋值运算： Table 35-4: Assignment Operators Notation Description parameter = value Simple assignment. Assigns value to parameter. parameter += value Addition. Equivalent to parameter = parameter + value. parameter -= value Subtraction. Equivalent to parameter = parameter - value. parameter *= value Multiplication. Equivalent to parameter = parameter * value. parameter /= value Integer division. Equivalent to parameter = parameter / value. parameter %= value Modulo. Equivalent to parameter = parameter % value. parameter++ Variable post-increment. Equivalent to parameter = parameter + 1 (however, see discussion below). parameter-- Variable post-decrement. Equivalent to parameter = parameter - 1. ++parameter Variable pre-increment. Equivalent to parameter = parameter + 1. --parameter Variable pre-decrement. Equivalent to parameter = parameter - 1. 表35-4: 赋值运算符 表示法 描述 parameter = value 简单赋值。给 parameter 赋值。 parameter += value 加。等价于 parameter = parameter + value。 parameter -= value 减。等价于 parameter = parameter – value。 parameter *= value 乘。等价于 parameter = parameter * value。 parameter /= value 整除。等价于 parameter = parameter / value。 parameter %= value 取模。等价于 parameter = parameter % value。 parameter++ 后缀自增变量。等价于 parameter = parameter + 1 (但，要看下面的讨论)。 parameter-- 后缀自减变量。等价于 parameter = parameter - 1。 ++parameter 前缀自增变量。等价于 parameter = parameter + 1。 --parameter 前缀自减变量。等价于 parameter = parameter - 1。 These assignment operators provide a convenient shorthand for many common arithmetic tasks. Of special interest are the increment (++) and decrement (--) operators, which increase or decrease the value of their parameters by one. This style of notation is taken from the C programming language and has been incorporated by several other programming languages, including bash. 这些赋值运算符为许多常见算术任务提供了快捷方式。特别关注一下自增（++）和自减（--）运算符，它们会把它们的参数值加1或减1。 这种风格的表示法取自C 编程语言并且被其它几种编程语言吸收，包括 bash。 The operators may appear either at the front of a parameter or at the end. While they both either increment or decrement the parameter by one, the two placements have a subtle difference. If placed at the front of the parameter, the parameter is incremented (or decre- mented) before the parameter is returned. If placed after, the operation is performed after the parameter is returned. This is rather strange, but it is the intended behavior. Here is a demonstration: 自增和自减运算符可能会出现在参数的前面或者后面。然而它们都是把参数值加1或减1，这两个位置有个微小的差异。 若运算符放置在参数的前面，参数值会在参数返回之前增加（或减少）。若放置在后面，则运算会在参数返回之后执行。 这相当奇怪，但这是它预期的行为。这里是个演示的例子： [me@linuxbox ~]$ foo=1
[me@linuxbox ~]$echo$((foo++))
1
[me@linuxbox ~]$echo$foo
2


If we assign the value of one to the variable foo and then increment it with the ++ operator placed after the parameter name, foo is returned with the value of one. However, if we look at the value of the variable a second time, we see the incremented value. If we place the ++ operator in front of the parameter, we get the more expected behavior:

[me@linuxbox ~]$foo=1 [me@linuxbox ~]$ echo $((++foo)) 2 [me@linuxbox ~]$ echo $foo 2  For most shell applications, prefixing the operator will be the most useful. 对于大多数 shell 应用来说，前缀运算符最有用。 The ++ and -- operators are often used in conjunction with loops. We will make some improvements to our modulo script to tighten it up a bit: 自增 ++ 和 自减 -- 运算符经常和循环操作结合使用。我们将改进我们的 modulo 脚本，让代码更紧凑些： #!/bin/bash # modulo2 : demonstrate the modulo operator for ((i = 0; i <= 20; ++i )); do if (((i % 5) == 0 )); then printf "<%d> "$i
else
printf "%d " $i fi done printf "\n"  #### # 位运算符 One class of operators manipulates numbers in an unusual way. These operators work at the bit level. They are used for certain kinds of low level tasks, often involving setting or reading bit-flags. 位运算符是一类以不寻常的方式操作数字的运算符。这些运算符工作在位级别的数字。它们被用在某类底层的任务中， 经常涉及到设置或读取位标志。 Table 35-5: Bit Operators Operator Description ~ Bitwise negation. Negate all the bits in a number. << Left bitwise shift. Shift all the bits in a number to the left. >> Right bitwise shift. Shift all the bits in a number to the right. & Bitwise AND. Perform an AND operation on all the bits in two numbers. | Bitwise OR. Perform an OR operation on all the bits in two numbers. ^ Bitwise XOR. Perform an exclusive OR operation on all the bits in two numbers. 表35-5: 位运算符 运算符 描述 ~ 按位取反。对一个数字所有位取反。 << 位左移. 把一个数字的所有位向左移动。 >> 位右移. 把一个数字的所有位向右移动。 & 位与。对两个数字的所有位执行一个 AND 操作。 | 位或。对两个数字的所有位执行一个 OR 操作。 ^ 位异或。对两个数字的所有位执行一个异或操作。 Note that there are also corresponding assignment operators (for example, <<=) for all but bitwise negation. 注意除了按位取反运算符之外，其它所有位运算符都有相对应的赋值运算符（例如，<<=）。 Here we will demonstrate producing a list of powers of 2, using the left bitwise shift operator: 这里我们将演示产生2的幂列表的操作，使用位左移运算符： [me@linuxbox ~]$ for ((i=0;i<8;++i)); do echo $((1<<i)); done 1 2 4 8 16 32 64 128  #### # 逻辑运算符 As we discovered in Chapter 27, the (( )) compound command supports a variety of comparison operators. There are a few more that can be used to evaluate logic. Here is the complete list: 正如我们在第27章中所看到的，复合命令 (( )) 支持各种各样的比较运算符。还有一些可以用来计算逻辑运算。 这里是比较运算符的完整列表： Table 35-6: Comparison Operators Operator Description <= Less than or equal to >= great than or equal to < less than > greater than == Equal to != Not equal to && Logical AND || Logical OR expr1?expr2:expr3 Comparison (ternary) operator. If expression expr1 evaluates to be non-zero (arithmetic true) then expr2, else expr3. 表35-6: 比较运算符 运算符 描述 <= 小于或相等 >= 大于或相等 < 小于 > 大于 == 相等 != 不相等 && 逻辑与 || 逻辑或 expr1?expr2:expr3 条件（三元）运算符。若表达式 expr1 的计算结果为非零值（算术真），则 执行表达式 expr2，否则执行表达式 expr3。 When used for logical operations, expressions follow the rules of arithmetic logic; that is, expressions that evaluate as zero are considered false, while non-zero expressions are considered true. The (( )) compound command maps the results into the shell’s normal exit codes: 当表达式用于逻辑运算时，表达式遵循算术逻辑规则；也就是，表达式的计算结果是零，则认为假，而非零表达式认为真。 该 (( )) 复合命令把结果映射成 shell 正常的退出码： [me@linuxbox ~]$ if ((1)); then echo "true"; else echo "false"; fi
true
[me@linuxbox ~]$if ((0)); then echo "true"; else echo "false"; fi false  The strangest of the logical operators is the ternary operator. This operator (which is modeled after the one in the C programming language) performs a standalone logical test. It can be used as a kind of if/then/else statement. It acts on three arithmetic expressions (strings won’t work), and if the first expression is true (or non-zero) the second expression is performed. Otherwise, the third expression is performed. We can try this on the command line: 最陌生的逻辑运算符就是这个三元运算符了。这个运算符（仿照于 C 编程语言里的三元运算符）执行一个单独的逻辑测试。 它用起来类似于 if/then/else 语句。它操作三个算术表达式（字符串不会起作用），并且若第一个表达式为真（或非零）， 则执行第二个表达式。否则，执行第三个表达式。我们可以在命令行中实验一下： [me@linuxbox~]$ a=0
[me@linuxbox~]$((a<1?++a:--a)) [me@linuxbox~]$ echo $a 1 [me@linuxbox~]$ ((a<1?++a:--a))
[me@linuxbox~]$echo$a
0


Here we see a ternary operator in action. This example implements a toggle. Each time the operator is performed, the value of the variable a switches from zero to one or vice versa.

Please note that performing assignment within the expressions is not straightforward.

When attempted, bash will declare an error:

[me@linuxbox ~]$a=0 [me@linuxbox ~]$ ((a<1?a+=1:a-=1))
bash: ((: a<1?a+=1:a-=1: attempted assignment to non-variable (error token is "-=1")


This problem can be mitigated by surrounding the assignment expression with parentheses:

[me@linuxbox ~]$((a<1?(a+=1):(a-=1)))  Next, we see a more complete example of using arithmetic operators in a script that produces a simple table of numbers: 下一步，我们看一个使用算术运算符更完备的例子，该示例产生一个简单的数字表格： #!/bin/bash # arith-loop: script to demonstrate arithmetic operators finished=0 a=0 printf "a\ta**2\ta**3\n" printf "=\t====\t====\n" until ((finished)); do b=$((a**2))
c=$((a**3)) printf "%d\t%d\t%d\n"$a $b$c
((a<10?++a:(finished=1)))
done


In this script, we implement an until loop based on the value of the finished variable. Initially, the variable is set to zero (arithmetic false) and we continue the loop until it becomes non-zero. Within the loop, we calculate the square and cube of the counter variable a. At the end of the loop, the value of the counter variable is evaluated. If it is less than 10 (the maximum number of iterations), it is incremented by one, else the variable finished is given the value of one, making finished arithmetically true, thereby terminating the loop. Running the script gives this result:

[me@linuxbox ~]$arith-loop a a**2 a**3 = ==== ==== 0 0 0 1 1 1 2 4 8 3 9 27 4 16 64 5 25 125 6 36 216 7 49 343 8 64 512 9 81 729 10 100 1000  ### # bc - 一种高精度计算器语言 We have seen how the shell can handle all types of integer arithmetic, but what if we need to perform higher math or even just use floating point numbers? The answer is, we can’t. At least not directly with the shell. To do this, we need to use an external program. There are several approaches we can take. Embedding Perl or AWK programs is one possible solution, but unfortunately, outside the scope of this book. Another approach is to use a specialized calculator program. One such program found on most Linux systems is called bc. 我们已经看到 shell 是可以处理所有类型的整型算术的，但是如果我们需要执行更高级的数学运算或仅使用浮点数，该怎么办？ 答案是，我们不能这样做。至少不能直接用 shell 完成此类运算。为此，我们需要使用外部程序。 有几种途径可供我们采用。嵌入的 Perl 或者 AWK 程序是一种可能的方案，但是不幸的是，超出了本书的内容大纲。 另一种方式就是使用一种专业的计算器程序。这样一个程序叫做 bc，在大多数 Linux 系统中都可以找到。 The bc program reads a file written in its own C-like language and executes it. A bc script may be a separate file or it may be read from standard input. The bc language supports quite a few features including variables, loops, and programmer-defined functions. We won’t cover bc entirely here, just enough to get a taste. bc is well documented by its man page. 该 bc 程序读取一个用它自己的类似于 C 语言的语法编写的脚本文件。一个 bc 脚本可能是一个分离的文件或者是从 标准输入读入。bc 语言支持相当少的功能，包括变量，循环和程序员定义的函数。这里我们不会讨论整个 bc 语言， 仅仅体验一下。查看 bc 的手册页，其文档整理得非常好。 Let’s start with a simple example. We’ll write a bc script to add 2 plus 2: 让我们从一个简单的例子开始。我们将编写一个 bc 脚本来执行2加2运算： /* A very simple bc script */ 2 + 2  The first line of the script is a comment. bc uses the same syntax for comments as the C programming language. Comments, which may span multiple lines, begin with /* and end with */. 脚本的第一行是一行注释。bc 使用和 C 编程语言一样的注释语法。注释，可能会跨越多行，开始于 /* 结束于 */ #### # 使用 bc If we save the bc script above as foo.bc, we can run it this way: 如果我们把上面的 bc 脚本保存为 foo.bc，然后我们就能这样运行它： [me@linuxbox ~]$ bc foo.bc
bc 1.06.94
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software
Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type warranty'.
4


If we look carefully, we can see the result at the very bottom, after the copyright message. This message can be suppressed with the -q (quiet) option. bc can also be used interactively:

[me@linuxbox ~]$bc -q 2 + 2 4 quit  When using bc interactively, we simply type the calculations we wish to perform, and the results are immediately displayed. The bc command quit ends the interactive session. 当使用 bc 交互模式时，我们简单地输入我们希望执行的运算，结果就立即显示出来。bc 的 quit 命令结束交互会话。 It is also possible to pass a script to bc via standard input: 也可能通过标准输入把一个脚本传递给 bc 程序： [me@linuxbox ~]$ bc < foo.bc
4


The ability to take standard input means that we can use here documents, here strings, and pipes to pass scripts. This is a here string example:

[me@linuxbox ~]$bc <<< "2+2" 4  #### # 一个脚本实例 As a real-world example, we will construct a script that performs a common calculation, monthly loan payments. In the script below, we use a here document to pass a script to bc: 作为一个真实世界的例子，我们将构建一个脚本，用于计算每月的还贷金额。在下面的脚本中， 我们使用了 here 文档把一个脚本传递给 bc： #!/bin/bash # loan-calc : script to calculate monthly loan payments PROGNAME=$(basename $0) usage () { cat <<- EOF Usage:$PROGNAME PRINCIPAL INTEREST MONTHS
Where:
PRINCIPAL is the amount of the loan.
INTEREST is the APR as a number (7% = 0.07).
MONTHS is the length of the loan's term.
EOF
}
if (($# != 3)); then usage exit 1 fi principal=$1
interest=$2 months=$3
bc <<- EOF
scale = 10
i = $interest / 12 p =$principal
n = $months a = p * ((i * ((1 + i) ^ n)) / (((1 + i) ^ n) - 1)) print a, "\n" EOF  When executed, the results look like this: 当脚本执行后，输出结果像这样： [me@linuxbox ~]$ loan-calc 135000 0.0775 180
475
1270.7222490000
`

This example calculates the monthly payment for a \$135,000 loan at 7.75% APR for 180 months (15 years). Notice the precision of the answer. This is determined by the value given to the special scale variable in the bc script. A full description of the bc scripting language is provided by the bc man page. While its mathematical notation is slightly different from that of the shell (bc more closely resembles C), most of it will be quite familiar, based on what we have learned so far.

### # 总结

In this chapter, we have learned about many of the little things that can be used to get the “real work” done in scripts. As our experience with scripting grows, the ability to effectively manipulate strings and numbers will prove extremely valuable. Our loan-calc script demonstrates that even simple scripts can be created to do some really useful things.

### # 额外加分

While the basic functionality of the loan-calc script is in place, the script is far from complete. For extra credit, try improving the loan-calc script with the following features:

• Full verification of the command line arguments

• A command line option to implement an “interactive” mode that will prompt the user to input the principal, interest rate, and term of the loan.

• A better format for the output.

• 完整的命令行参数验证

• 用一个命令行选项来实现“交互”模式，提示用户输入本金、利率和贷款期限

• 输出格式美化