第三十四章: 字符串和数字
34.1 参数扩展
34.1.1 基本参数
参数扩展的最简单形式体现在平常对变量的使用中, 例如 $a 扩展后称为变量a所包含的内容, 简单参数也可以被大括号包围, 例如 ${a}.
34.1.2 空变量扩展的管理
有的参数扩展用于处理不存在的变量和空变量, 形式如下:
${parameter:-word}
如果parameter未被设定或者是空参数, 则其扩展为word的值; 如果parameter非空, 则扩展为parameter的值.
另外一种扩展形式如下:
${parameter:=word}
如果parameter未被设定或者是空参数, 则其扩展为word的值, 同时将值赋值给parameter; 如果parameter非空, 则扩展为parameter的值.
需要注意的是, 位置参数和其他特殊参数不能使用这种方式赋值.
第三种扩展形式如下:
${parameter:?word}
如果parameter未被设定或者是空参数, 则扩展会导致脚本出错而退出, 同时word的内容会输出到标准错误; 如果parameter非空, 则扩展为parameter的值.
第四种扩展形式如下:
${parameter:+word}
若parameter未设定或为空, 将不产生任何扩展; 若parameter非空, word的值将取代parameter的值, 但是parameter的值不发生变化.
34.1.3 返回变量名的扩展
shell具有返回变量名的功能, 形式如下:
${!prefix*}
${!prefix@}
该扩展返回当前以prefix开头的变量名, 根据bash文档, 这两种扩展形式执行效果相同.
34.1.4 字符串操作
对字符串的操作存在着大量的扩展集合, 其中一些扩展尤其适用于对路径名的操作, 形式如下:
${#parameter}
扩展为parameter内包含的字符串的长度, 一般来说参数parameter是个字符串, 如果parameter是 @ 或 *, 那么扩展结果就是位置参数的个数.
提取字符串的扩展形式如下:
${parameter:offset}
${parameter:offset:length}
这个扩展可以提取一部分包含在参数parameter中的字符串, 扩展以offset字符开始, 直到字符串末尾, 除非length特别指定.
如果offset的值为负, 则表示从字符串末尾开始, 需要注意的是, 负值前必须有一个空格, 如果有length的话, length不能小于0.
如果参数是@的话, 扩展的结果则是从offset开始, length为位置参数.
去除字符串内容的扩展形式如下:
${parameter#pattern}
${parameter##pattern}
${parameter%pattern}
${parameter%%pattern}
根据pattern的定义, 这些扩展去除了包含在pattern中的字符串的主要部分, pattern是一个通配符模式. # 形式去除最短匹配, ## 形式去除最常匹配, %和%%形式则是从字符串末尾去除文本.
对字符串进行替换的扩展如下:
${parameter/pattern/string}
${parameter//pattern/string}
${parameter/#pattern/string}
${parameter/%pattern/string}
这种形式的展开对 parameter 的内容执行查找和替换操作. 如果找到了匹配通配符 pattern 的文本, 则用 string 的内容替换它. 在正常形式下, 只有第一个匹配项会被替换掉, 在 // 形式下, 所有的匹配项都会被替换掉. /# 要求匹配项出现在字符串的开头, 而 /% 要求匹配项出现在字符串的末尾. /string 可以省略掉, 这样会导致删除匹配的文本.
参数扩展是一个重要的功能, 进行字符串操作的扩展可以替代其他常用的命令, 例如set和cut命令. 扩展通过取代外部程序, 也改善了脚本的执行效率. 使用参数扩展修改 longest-word程序如下:
#!/bin/bash
# longest-word3 : find longest string in a file
for i; do
if [[ -r $i ]]; then
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
34.1.5 大小写转换
最新的 bash 版本已经支持字符串的大小写转换了, bash 有四个参数展开和 declare 命令的两个选项来支持大小写转换.
这个 declare 命令可以用来把字符串规范成大写或小写字符. 使用 declare 命令, 我们能强制一个变量总是包含所需的格式, 无论如何赋值给它:
#!/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
在上面的脚本中, 我们使用 declare 命令来创建两个变量upper 和 lower. 并设定了它们能够包含的格式.
四个可以执行大小写转换操作的参数展开如下表:
格式 | 结果 |
---|---|
${parameter,,} | 把 parameter 的值全部展开成小写字母 |
${parameter,} | 仅仅把 parameter 的第一个字符展开成小写字母 |
${parameter^^} | 把 parameter 的值全部转换成大写字母 |
${parameter^} | 仅仅把 parameter 的第一个字符转换成大写字母 |
下面的脚本演示了这些展开格式:
#!/bin/bash
# ul-param - demonstrate case conversion via parameter expansion
if [[ $1 ]]; then
echo ${1,,}
echo ${1,}
echo ${1^^}
echo ${1^}
fi
34.2 算术计算和扩展
算术扩展的基本形式如下:
$((expression))
34.2.1 数字进制
shell支持任何进制表示的整数, 如下表:
符号 | 描述 |
---|---|
Number | 十进制, 默认情况 |
0Number | 八进制, 以0开始的数字 |
0xNumber | 十六进制, 以0x开始 |
base#Number | base进制的数字 |
例如:
echo $((0xff))
echo $((2#11111111))
34.2.2 一元运算符
有两种一元运算符: + 和 -, 分别用来表示数字的正负.
34.2.3 简单算术
shell支持的普通算术运算符如下表:
操作符 | 描述 |
---|---|
+ | 加法 |
- | 减法 |
* | 乘法 |
/ | 除法 |
** | 求幂 |
% | 取模, 即求余数 |
由于shell的算术运算符仅适用于整数, 则除法的结果永远是完整的数字. 一个shell算术运算的实例如下:
#!/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"
以上代码突出显示5的倍数.
34.2.4 赋值
每当赋值给一个变量一个值时, 就是赋值操作.
foo=5
if (( foo=5 )); then echo "It is true."; fi
shell支持的一些其他赋值语句如下:
运算符 | 描述 |
---|---|
parameter=value | 简单赋值 |
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 |
使用++操作符修改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"
34.2.5 位操作
shell支持的位操作符如下表:
操作符 | 描述 | |
---|---|---|
~ | 按位取反 | |
<< | 按位左移 | |
>> | 按位右移 | |
& | 按位与 | |
\ | 按位或 | |
^ | 按位异或 |
除按位取反之外, 对于位操作符也存在对应的赋值操作, 例如 <<= .
以下为一个产生2的次方的数字的代码:
for ((i=0;i<8;++i)); do echo $((1<<i)); done
34.2.6 逻辑操作
(()) 命令支持的用于逻辑判断的操作符如下表:
操作符 | 描述 | ||
---|---|---|---|
<= | 小于或等于 | ||
>= | 大于或等于 | ||
< | 小于 | ||
> | 大于 | ||
== | 等于 | ||
!= | 不等于 | ||
&& | 逻辑与 | ||
\ | \ | 逻辑或 | |
expr1?expr2:expr3 | 三元比较操作, 如果expr1为true, 那么执行expr2, 否则执行expr3 |
当使用逻辑操作时, 表达式遵循算术逻辑的规则, 即值为0表示false, 值为非0表示true.
注意在表达式内的赋值操作不能简单使用, 需要用括号来包围赋值表达式, 例如:
((a<1?(a+=1):(a-=1)))
一个输出数字表的脚本如下:
#!/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
34.3 bc: 一种任意精度计算语言
bc程序读取一个使用类C语言编写的程序文件, 并执行它. bc脚本可以是一个单独的文件, 也可以从标准输入中读取. bc语言支持很多功能, 包括变量, 循环以及程序员自定义的函数.
一个 2+2 的bc脚本如下:
/* A very simple bc script */
2 + 2
脚本的第一行是注释, bc使用和C语言相同的注释语法.
34.3.1 bc的使用
如果将上述脚本保存为 foo.bc , 那么可以使用以下命令运行它:
bc foo.bc
默认情况下bc会输出版权信息, 可以使用 -q 选项禁止显示. bc也可以交互的使用, 这时只需要简单的输入值, 计算结果会立刻被显示, 使用quit命令可以结束交互会话.
通过标准输入传递一个脚本到bc是可行的:
bc < foo.bc
也可以使用嵌入文档, 嵌入字符串和管道传递文本:
bc <<< "2+2"
34.3.2 脚本例子
一个简单的计算按月偿还贷款的脚本如下:
#!/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
以上代码使用嵌入文档传递脚本到bc. bc脚本中的scale命令决定了精度.
34.4 本章结尾语
34.5 附加项
- 《ash Hackers Wiki》对参数展开有一个很好的论述:
http://wiki.bash-hackers.org/syntax/pe
- 《Bash 参考手册》也介绍了这个:
http://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameter-Expansion
- Wikipedia 上面有一篇很好的文章描述了位运算:
http://en.wikipedia.org/wiki/Bit_operation
- 一篇关于三元运算的文章:
http://en.wikipedia.org/wiki/Ternary_operation
- 还有一个对计算还贷金额公式的描述, 我们的 loan-calc 脚本中用到了这个公式: