从本文开始,打算结合平时积累和进一步实践,通过一些范例来介绍Shell编程。因为范例往往能够给人以学有所用的感觉,而且给人以动手实践的机会,从而激发人的学习热情。
考虑到易读性,这些范例将非常简单,但是实用,希望它们能够成为我们解决日常问题的参照物或者是“茶余饭后”的小点心,当然这些“点心”肯定还有值得探讨、优化的地方。
更复杂有趣的例子请参考 Advanced Bash-Scripting Guide (一本深入学习 Shell 脚本艺术的书籍)。
范例:对某个数加 1
$ i=0;
$ ((i++))
$ echo $i
1
$ let i++
$ echo $i
2
$ expr $i + 1
3
$ echo $i
2
$ echo $i 1 | awk '{printf $1+$2}'
3
说明: expr
之后的 $i
,+
,1 之间有空格分开。如果进行乘法运算,需要对运算符进行转义,否则 Shell 会把乘号解释为通配符,导致语法错误; awk
后面的 $1
和 $2
分别指 $i
和 1,即从左往右的第 1 个和第 2 个数。
用 Shell 的内置命令查看各个命令的类型如下:
$ type type
type is a shell builtin
$ type let
let is a shell builtin
$ type expr
expr is hashed (/usr/bin/expr)
$ type bc
bc is hashed (/usr/bin/bc)
$ type awk
awk is /usr/bin/awk
从上述演示可看出: let
是 Shell 内置命令,其他几个是外部命令,都在 /usr/bin
目录下。而 expr
和 bc
因为刚用过,已经加载在内存的 hash
表中。这将有利于我们理解在上一章介绍的脚本多种执行方法背后的原理。
说明:如果要查看不同命令的帮助,对于 let
和 type
等 Shell 内置命令,可以通过 Shell 的一个内置命令 help
来查看相关帮助,而一些外部命令可以通过 Shell 的一个外部命令 man
来查看帮助,用法诸如 help let
,man expr
等。
范例:从 1 加到某个数
#!/bin/bash
# calc.sh
i=0;
while [ $i -lt 10000 ]
do
((i++))
done
echo $i
说明:这里通过 while [ 条件表达式 ]; do .... done
循环来实现。-lt
是小于号 <
,具体见 test
命令的用法:help test
。
如何执行该脚本?
办法一:直接把脚本文件当成子 Shell (Bash)的一个参数传入
$ bash calc.sh
$ type bash
bash is hashed (/bin/bash)
办法二:是通过 bash
的内置命令 .
或 source
执行
$ . ./calc.sh
或
$ source ./calc.sh
$ type .
. is a shell builtin
$ type source
source is a shell builtin
办法三:是修改文件为可执行,直接在当前 Shell 下执行
$ chmod ./calc.sh
$ ./calc.sh
下面,逐一演示用其他方法计算变量加一,即把 ((i++))
行替换成下面的某一个:
let i++;
i=$(expr $i + 1)
i=$(echo $i+1|bc)
i=$(echo "$i 1" | awk '{printf $1+$2;}')
比较计算时间如下:
$ time calc.sh
10000
real 0m1.319s
user 0m1.056s
sys 0m0.036s
$ time calc_let.sh
10000
real 0m1.426s
user 0m1.176s
sys 0m0.032s
$ time calc_expr.sh
1000
real 0m27.425s
user 0m5.060s
sys 0m14.177s
$ time calc_bc.sh
1000
real 0m56.576s
user 0m9.353s
sys 0m24.618s
$ time ./calc_awk.sh
100
real 0m11.672s
user 0m2.604s
sys 0m2.660s
说明: time
命令可以用来统计命令执行时间,这部分时间包括总的运行时间,用户空间执行时间,内核空间执行时间,它通过 ptrace
系统调用实现。
通过上述比较可以发现 (())
的运算效率最高。而 let
作为 Shell 内置命令,效率也很高,但是 expr
,bc
,awk
的计算效率就比较低。所以,在 Shell 本身能够完成相关工作的情况下,建议优先使用 Shell 本身提供的功能。但是 Shell 本身无法完成的功能,比如浮点运算,所以就需要外部命令的帮助。另外,考虑到 Shell 脚本的可移植性,在性能不是很关键的情况下,不要使用某些 Shell 特有的语法。
let
,expr
,bc
都可以用来求模,运算符都是 %
,而 let
和 bc
可以用来求幂,运算符不一样,前者是 **
,后者是 ^
。例如:
范例:求模
$ expr 5 % 2
1
$ let i=5%2
$ echo $i
1
$ echo 5 % 2 | bc
1
$ ((i=5%2))
$ echo $i
1
范例:求幂
$ let i=5**2
$ echo $i
25
$ ((i=5**2))
$ echo $i
25
$ echo "5^2" | bc
25
进制转换也是比较常用的操作,可以用 Bash
的内置支持也可以用 bc
来完成,例如把 8 进制的 11 转换为 10 进制,则可以:
$ echo "obase=10;ibase=8;11" | bc -l
9
$ echo $((8#11))
9
上面都是把某个进制的数转换为 10 进制的,如果要进行任意进制之间的转换还是 bc
比较灵活,因为它可以直接用 ibase
和 obase
分别指定进制源和进制转换目标。
范例:ascii 字符编码
如果要把某些字符串以特定的进制表示,可以用 od
命令,例如默认的分隔符 IFS
包括空格、 TAB
以及换行,可以用 man ascii
佐证。
$ echo -n "$IFS" | od -c
0000000 t n
0000003
$ echo -n "$IFS" | od -b
0000000 040 011 012
0000003