Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting | ||
---|---|---|
Prev | Chapter 12. External Filters, Programs and Commands | Next |
将一个正数分解为多个素数.
bash$ factor 27417 27417: 3 13 19 37 |
Bash 不能处理浮点运算, 并且缺乏特定的一些操作,这些操作都是一些重要的计算功能.幸运的是, bc 可以解决这个问题.
bc 不仅仅是个多功能灵活的精确的工具, 而且它还提供许多编程语言才具备的一些方便的功能.
bc 比较类似于 C 语言的语法.
因为它是一个完整的 UNIX 工具, 所以它可以用在管道中, bc 在脚本中也是很常用的.
这里有一个简单的使用 bc 命令的模版可以用来在计算脚本中的变量. 用在命令替换 中.
variable=$(echo "OPTIONS; OPERATIONS" | bc) |
Example 12-42. 按月偿还贷款
1 #!/bin/bash 2 # monthlypmt.sh: 计算按月偿还贷款的数量. 3 4 5 # 这份代码是一份修改版本, 原始版本在 "mcalc" (贷款计算)包中, 6 #+ 这个包的作者是 Jeff Schmidt 和 Mendel Cooper (本书作者). 7 # http://www.ibiblio.org/pub/Linux/apps/financial/mcalc-1.6.tar.gz [15k] 8 9 echo 10 echo "Given the principal, interest rate, and term of a mortgage," 11 echo "calculate the monthly payment." 12 13 bottom=1.0 14 15 echo 16 echo -n "Enter principal (no commas) " 17 read principal 18 echo -n "Enter interest rate (percent) " # 如果是 12%, 那就键入 "12", 别输入 ".12". 19 read interest_r 20 echo -n "Enter term (months) " 21 read term 22 23 24 interest_r=$(echo "scale=9; $interest_r/100.0" | bc) # 转换成小数. 25 # "scale" 指定了有效数字的个数. 26 27 28 interest_rate=$(echo "scale=9; $interest_r/12 + 1.0" | bc) 29 30 31 top=$(echo "scale=9; $principal*$interest_rate^$term" | bc) 32 33 echo; echo "Please be patient. This may take a while." 34 35 let "months = $term - 1" 36 # ==================================================================== 37 for ((x=$months; x > 0; x--)) 38 do 39 bot=$(echo "scale=9; $interest_rate^$x" | bc) 40 bottom=$(echo "scale=9; $bottom+$bot" | bc) 41 # bottom = $(($bottom + $bot")) 42 done 43 # ==================================================================== 44 45 # -------------------------------------------------------------------- 46 # Rick Boivie 给出了一个对上边循环的修改, 47 #+ 这个修改更加有效率, 将会节省大概 2/3 的时间. 48 49 # for ((x=1; x <= $months; x++)) 50 # do 51 # bottom=$(echo "scale=9; $bottom * $interest_rate + 1" | bc) 52 # done 53 54 55 # 然后他又想出了一个更加有效率的版本, 56 #+ 将会节省 95% 的时间! 57 58 # bottom=`{ 59 # echo "scale=9; bottom=$bottom; interest_rate=$interest_rate" 60 # for ((x=1; x <= $months; x++)) 61 # do 62 # echo 'bottom = bottom * interest_rate + 1' 63 # done 64 # echo 'bottom' 65 # } | bc` # 在命令替换中嵌入一个 'for 循环'. 66 # -------------------------------------------------------------------------- 67 # On the other hand, Frank Wang suggests: 68 # bottom=$(echo "scale=9; ($interest_rate^$term-1)/($interest_rate-1)" | bc) 69 70 # 因为 . . . 71 # 在循环后边的算法 72 #+ 事实上是一个等比数列的求和公式. 73 # 求和公式是 e0(1-q^n)/(1-q), 74 #+ e0 是第一个元素 并且 q=e(n+1)/e(n) 75 #+ 和 n 是元素的数量. 76 # -------------------------------------------------------------------------- 77 78 79 # let "payment = $top/$bottom" 80 payment=$(echo "scale=2; $top/$bottom" | bc) 81 # 使用2位有效数字来表示美元和美分. 82 83 echo 84 echo "monthly payment = \$$payment" # 在总和的前边显示美元符号. 85 echo 86 87 88 exit 0 89 90 91 # 练习: 92 # 1) 处理输入允许本金总数中的逗号. 93 # 2) 处理输入允许按照百分号和小数点的形式输入利率. 94 # 3) 如果你真正想好好编写这个脚本, 95 # 那么就扩展这个脚本让它能够打印出完整的分期付款表. |
Example 12-43. 数制转换
1 #!/bin/bash 2 ########################################################################## 3 # 脚本 : base.sh - 用不同的数值来打印数字 (Bourne Shell) 4 # 作者 : Heiner Steven (heiner.steven@odn.de) 5 # 日期 : 07-03-95 6 # 类型 : 桌面 7 # $Id: base.sh,v 1.2 2000/02/06 19:55:35 heiner Exp $ 8 # ==> 上边这行是 RCS ID 信息. 9 ########################################################################## 10 # 描述 11 # 12 # Changes 13 # 21-03-95 stv fixed error occuring with 0xb as input (0.2) 14 ########################################################################## 15 16 # ==> 在本书中使用这个脚本通过了作者的授权. 17 # ==> 注释是本书作者添加的. 18 19 NOARGS=65 20 PN=`basename "$0"` # 程序名 21 VER=`echo '$Revision: 1.2 $' | cut -d' ' -f2` # ==> VER=1.2 22 23 Usage () { 24 echo "$PN - print number to different bases, $VER (stv '95) 25 usage: $PN [number ...] 26 27 If no number is given, the numbers are read from standard input. 28 A number may be 29 binary (base 2) starting with 0b (i.e. 0b1100) 30 octal (base 8) starting with 0 (i.e. 014) 31 hexadecimal (base 16) starting with 0x (i.e. 0xc) 32 decimal otherwise (i.e. 12)" >&2 33 exit $NOARGS 34 } # ==> 打印出用法信息的函数. 35 36 Msg () { 37 for i # ==> 省略 [list] . 38 do echo "$PN: $i" >&2 39 done 40 } 41 42 Fatal () { Msg "$@"; exit 66; } 43 44 PrintBases () { 45 # 决定数值的数制 46 for i # ==> 省略 [list]... 47 do # ==> 所以是对命令行参数进行操作. 48 case "$i" in 49 0b*) ibase=2;; # 2进制 50 0x*|[a-f]*|[A-F]*) ibase=16;; # 16进制 51 0*) ibase=8;; # 8进制 52 [1-9]*) ibase=10;; # 10进制 53 *) 54 Msg "illegal number $i - ignored" 55 continue;; 56 esac 57 58 # 去掉前缀, 将16进制数字转换为大写(bc需要大写) 59 number=`echo "$i" | sed -e 's:^0[bBxX]::' | tr '[a-f]' '[A-F]'` 60 # ==>使用":" 作为sed分隔符, 而不使用"/". 61 62 # 将数字转换为10进制 63 dec=`echo "ibase=$ibase; $number" | bc` # ==> 'bc' 是个计算工具. 64 case "$dec" in 65 [0-9]*) ;; # 数字没问题 66 *) continue;; # 错误: 忽略 67 esac 68 69 # 在一行上打印所有的转换后的数字. 70 # ==> 'here document' 提供命令列表给'bc'. 71 echo `bc <<! 72 obase=16; "hex="; $dec 73 obase=10; "dec="; $dec 74 obase=8; "oct="; $dec 75 obase=2; "bin="; $dec 76 ! 77 ` | sed -e 's: : :g' 78 79 done 80 } 81 82 while [ $# -gt 0 ] 83 # ==> 这里必须使用一个 "while 循环", 84 # ==>+ 因为所有的 case 都可能退出循环或者 85 # ==>+ 结束脚本. 86 # ==> (感谢, Paulo Marcel Coelho Aragao.) 87 do 88 case "$1" in 89 --) shift; break;; 90 -h) Usage;; # ==> 帮助信息. 91 -*) Usage;; 92 *) break;; # 第一个数字 93 esac # ==> 对于非法输入更严格检查是非常有用的. 94 shift 95 done 96 97 if [ $# -gt 0 ] 98 then 99 PrintBases "$@" 100 else # 从标准输入中读取 101 while read line 102 do 103 PrintBases $line 104 done 105 fi 106 107 108 exit 0 |
调用 bc 的另一种可选的方法就是使用 here document ,并把它嵌入到 命令替换 块中. 当一个脚本需要将一个选项列表和多个命令传递到 bc 中时, 这种方法就显得非常合适.
1 variable=`bc << LIMIT_STRING 2 options 3 statements 4 operations 5 LIMIT_STRING 6 ` 7 8 ...or... 9 10 11 variable=$(bc << LIMIT_STRING 12 options 13 statements 14 operations 15 LIMIT_STRING 16 ) |
Example 12-44. 使用 "here document" 来调用 bc
1 #!/bin/bash 2 # 使用命令替换来调用 'bc' 3 # 并与 'here document' 相结合. 4 5 6 var1=`bc << EOF 7 18.33 * 19.78 8 EOF 9 ` 10 echo $var1 # 362.56 11 12 13 # $( ... ) 这种标记法也可以. 14 v1=23.53 15 v2=17.881 16 v3=83.501 17 v4=171.63 18 19 var2=$(bc << EOF 20 scale = 4 21 a = ( $v1 + $v2 ) 22 b = ( $v3 * $v4 ) 23 a * b + 15.35 24 EOF 25 ) 26 echo $var2 # 593487.8452 27 28 29 var3=$(bc -l << EOF 30 scale = 9 31 s ( 1.7 ) 32 EOF 33 ) 34 # 返回弧度为1.7的正弦. 35 # "-l" 选项将会调用 'bc' 算数库. 36 echo $var3 # .991664810 37 38 39 # 现在, 在函数中试一下... 40 hyp= # 声明全局变量. 41 hypotenuse () # 计算直角三角形的斜边. 42 { 43 hyp=$(bc -l << EOF 44 scale = 9 45 sqrt ( $1 * $1 + $2 * $2 ) 46 EOF 47 ) 48 # 不幸的是, 不能从bash 函数中返回浮点值. 49 } 50 51 hypotenuse 3.68 7.31 52 echo "hypotenuse = $hyp" # 8.184039344 53 54 55 exit 0 |
Example 12-45. 计算圆周率
dc (桌面计算器desk calculator) 工具是面向栈的并且使用 RPN (逆波兰表达式 "Reverse Polish Notation" 又叫"后缀表达式"). 与 bc 命令很相像 , 但是这个工具具备好多只有编程语言才具备的能力.(正常表达式 逆波兰表达式 a+b a,b,+ a+(b-c) a,b,c,-,+ a+(b-c)*d a,d,b,c,-,*,+)
绝大多数人都避免使用这个工具, 因为它需要非直觉的 RPN 输入. 但是, 它却有特定的用途.
Example 12-46. 将10进制数字转换为16进制数字
1 #!/bin/bash 2 # hexconvert.sh: 将10进制数字转换为16进制数字 3 4 E_NOARGS=65 # 缺命令行参数错误. 5 BASE=16 # 16进制. 6 7 if [ -z "$1" ] 8 then 9 echo "Usage: $0 number" 10 exit $E_NOARGS 11 # 需要一个命令行参数. 12 fi 13 # 练习: 添加命令行参数检查. 14 15 16 hexcvt () 17 { 18 if [ -z "$1" ] 19 then 20 echo 0 21 return # 如果没有参数传递到这个函数中就 "return" 0. 22 fi 23 24 echo ""$1" "$BASE" o p" | dc 25 # "o" 设置输出的基数(数制). 26 # "p" 打印栈顶. 27 # 察看 dc 的 man 页来了解其他的选项. 28 return 29 } 30 31 hexcvt "$1" 32 33 exit 0 |
通过仔细学习 dc 命令的 info 页, 可以更深入的理解这个复杂的命令. 但是, 有一些精通 dc巫术 的小组经常会炫耀他们使用这个强大而又晦涩难懂的工具时的一些技巧, 并以此为乐.
bash$ echo "16i[q]sa[ln0=aln100%Pln100/snlbx]sbA0D68736142snlbxq" | dc" Bash |
Example 12-47. 因子分解
1 #!/bin/bash 2 # factr.sh: 分解约数 3 4 MIN=2 # 如果比这个数小就不行了. 5 E_NOARGS=65 6 E_TOOSMALL=66 7 8 if [ -z $1 ] 9 then 10 echo "Usage: $0 number" 11 exit $E_NOARGS 12 fi 13 14 if [ "$1" -lt "$MIN" ] 15 then 16 echo "Number to factor must be $MIN or greater." 17 exit $E_TOOSMALL 18 fi 19 20 # 练习: 添加类型检查 (防止非整型的参数). 21 22 echo "Factors of $1:" 23 # --------------------------------------------------------------------------------- 24 echo "$1[p]s2[lip/dli%0=1dvsr]s12sid2%0=13sidvsr[dli%0=1lrli2+dsi!>.]ds.xd1<2" | dc 25 # --------------------------------------------------------------------------------- 26 # 上边这行代码是 Michel Charpentier 编写的<charpov@cs.unh.edu>. 27 # 在此使用经过授权 (thanks). 28 29 exit 0 |
在脚本中使用浮点运算的另一种方法是使用 awk 内建的数学运算函数, 可以用在shell wrapper中.
Example 12-48. 计算直角三角形的斜边
1 #!/bin/bash 2 # hypotenuse.sh: 返回直角三角形的斜边. 3 # ( 直角边长的平方和,然后对和取平方根) 4 5 ARGS=2 # 需要将2个直角边作为参数传递进来. 6 E_BADARGS=65 # 错误的参数值. 7 8 if [ $# -ne "$ARGS" ] # 测试传递到脚本中的参数值. 9 then 10 echo "Usage: `basename $0` side_1 side_2" 11 exit $E_BADARGS 12 fi 13 14 15 AWKSCRIPT=' { printf( "%3.7f\n", sqrt($1*$1 + $2*$2) ) } ' 16 # 命令 / 传递给awk的参数 17 18 19 # 现在, 将参数通过管道传递给awk. 20 echo -n "Hypotenuse of $1 and $2 = " 21 echo $1 $2 | awk "$AWKSCRIPT" 22 23 exit 0 |