将一个正数分解为多个素数.
- root@ubuntu:~/resource/shell-study/0622-2013# factor 2374
- 2374: 2 1187
- root@ubuntu:~/resource/shell-study/0622-2013# factor 27417
- 27417: 3 13 19 37
- root@ubuntu:~/resource/shell-study/0622-2013#
bc
Bash不能处理浮点运算, 并且缺乏特定的一些操作, 这些操作都是一些重要的计算功能. 幸运的是, bc可以解决这个问题.
bc不仅仅是个多功能灵活的精确计算工具, 且它还提供许多编程语言才具备的一些方便功能,bc比较类似于C语言的语法,因为它是一个完整的UNIX工具, 所以它可以用在pipe中, bc在脚本中也是很常用的.
这里有一个简单的使用bc命令的模版, 可以用来计算脚本中的变量. 这个模版经常用于命令替换中.
variable=$(echo "OPTIONS; OPERATIONS" | bc)
一个实例,不过不推荐你去看其中的算法,你只要只要怎么用bc去计算就够了
- #!/bin/bash
- # 计算按月偿还贷款的数量.
- echo
- echo "Given the principal, interest rate, and term of a mortgage,"
- echo "calculate the monthly payment."
- bottom=1.0
- echo
- echo -n "Enter principal (no commas) "
- read principal
- echo -n "Enter interest rate (percent)"#如果是12%,那就键入"12",而不是".12".
- read interest_r
- echo -n "Enter term (months) "
- read term
- interest_r=$(echo "scale=9; $interest_r/100.0" | bc) # 转换成小数.
- # "scale"指定了有效数字的个数.
- interest_rate=$(echo "scale=9; $interest_r/12 + 1.0" | bc)
- top=$(echo "scale=9; $principal*$interest_rate^$term" | bc)
- echo; echo "Please be patient. This may take a while."
- let "months = $term - 1"
- # ====================================================================
- for ((x=$months; x > 0; x--))
- do
- bot=$(echo "scale=9; $interest_rate^$x" | bc)
- bottom=$(echo "scale=9; $bottom+$bot" | bc)
- # bottom = $(($bottom + $bot"))
- done
- # ====================================================================
- # --------------------------------------------------------------------
- # Rick Boivie给出了一个对上边循环的修改方案,
- #+ 这个修改更加有效率, 将会节省大概2/3的时间.
- # for ((x=1; x <= $months; x++))
- # do
- # bottom=$(echo "scale=9; $bottom * $interest_rate + 1" | bc)
- # done
- # 然后他又想出了一个更加有效率的版本,
- #+ 将会节省95%的时间!
- # bottom=`{
- # echo "scale=9; bottom=$bottom; interest_rate=$interest_rate"
- # for ((x=1; x <= $months; x++))
- # do
- # echo 'bottom = bottom * interest_rate + 1'
- # done
- # echo 'bottom'
- # } | bc` # 在命令替换中嵌入一个'for循环'.
- # -------------------------------------------------------------------------
- # 另一方面, Frank Wang建议:
- # bottom=$(echo "scale=9; nterest_rate^$term-1)/($interest_rate-1)" | bc)
- # 因为 . . .
- # 在循环后边的算法
- #+ 事实上是一个等比数列的求和公式.
- # 求和公式是 e0(1-q^n)/(1-q),
- #+ e0是第一个元素, q=e(n+1)/e(n),
- #+ n是元素数量.
- # --------------------------------------------------------------------------
- # let "payment = $top/$bottom"
- payment=$(echo "scale=2; $top/$bottom" | bc)
- # 使用2位有效数字来表示美元和美分.
- echo
- echo "monthly payment = \$$payment" # 在总和的前边显示美元符号.
- echo
- exit 0
- root@ubuntu:~/resource/shell-study/0622-2013# ./test10.sh
- Given the principal, interest rate, and term of a mortgage,
- calculate the monthly payment.
- Enter principal (no commas) 50000
- Enter interest rate (percent)#如果是12%,那就键入12,而不是.12.5
- Enter term (months) 12
- Please be patient. This may take a while.
- monthly payment = $4280.37
- root@ubuntu:~/resource/shell-study/0622-2013#
- #!/bin/bash
- ##########################################################################
- # 脚本 : base.sh - 用不同的数制来打印数字 (Bourne Shell)
- # 作者 : Heiner Steven (heiner.steven@odn.de)
- # 日期 : 07-03-95
- # 类型 : 桌面
- # $Id: base.sh,v 1.2 2000/02/06 19:55:35 heiner Exp $
- # ==> 上边这行是RCS ID信息.
- ##########################################################################
- # 描述
- #
- # 修改纪录
- # 21-03-95 stv fixed error occuring with 0xb as input (0.2)
- ##########################################################################
- # ==> 在本书中使用这个脚本通过了原作者的授权.
- # ==> 注释是本书作者添加的.
- NOARGS=65
- PN=`basename "$0"` # 程序名
- VER=`echo '$Revision: 1.2 $' | cut -d' ' -f2` # ==> VER=1.2
- Usage () {
- echo "$PN - print number to different bases, $VER (stv '95)
- usage: $PN [number ...]
- If no number is given, the numbers are read from standard input.
- A number may be
- binary (base 2) starting with 0b (i.e. 0b1100)
- octal (base 8) starting with 0 (i.e. 014)
- hexadecimal (base 16) starting with 0x (i.e. 0xc)
- decimal otherwise (i.e. 12)" >&2
- exit $NOARGS
- } # ==> 打印出用法信息的函数.
- Msg () {
- for i # ==> 省略[list].
- do echo "$PN: $i" >&2
- done
- }
- Fatal () { Msg "$@"; exit 66; }
- PrintBases () {
- # 决定数字的数制
- for i # ==> 省略[list]...
- do # ==> 所以是对命令行参数进行操作.
- case "$i" in
- 0b*) ibase=2;; # 2进制
- 0x*|[a-f]*|[A-F]*) ibase=16;; # 16进制
- 0*) ibase=8;; # 8进制
- [1-9]*) ibase=10;; # 10进制
- *)
- Msg "illegal number $i - ignored"
- continue;;
- esac
- # 去掉前缀, 将16进制数字转换为大写(bc命令需要这么做)
- number=`echo "$i" | sed -e 's:^0[bBxX]::' | tr '[a-f]' '[A-F]'`
- # ==> 使用":" 作为sed分隔符, 而不使用"/".
- # 将数字转换为10进制
- dec=`echo "ibase=$ibase; $number" | bc` # ==> 'bc'是个计算工具.
- case "$dec" in
- [0-9]*) ;; # 数字没问题
- *) continue;; # 错误: 忽略
- esac
- # 在一行上打印所有转换后的数字.
- # ==> 'here document'提供命令列表给'bc'.
- echo `bc <<!
- obase=16; "hex="; $dec
- obase=10; "dec="; $dec
- obase=8; "oct="; $dec
- obase=2; "bin="; $dec
- !` | sed -e 's: : :g'
- done
- }
- while [ $# -gt 0 ]
- # ==> 这里必须使用一个"while循环",
- # ==>+ 因为所有的case都可能退出循环或者
- # ==>+ 结束脚本.
- # ==> (感谢, Paulo Marcel Coelho Aragao.)
- do
- case "$1" in
- --) shift; break;;
- -h) Usage;; # ==> 帮助信息.
- -*) Usage;;
- *) break;; # 第一个数字
- esac # ==> 对于非法输入进行更严格检查是非常有用的.
- shift
- done
- if [ $# -gt 0 ]
- then
- PrintBases "$@"
- else # 从stdin中读取
- while read line
- do
- PrintBases $line
- done
- fi
- exit 0
- root@ubuntu:~/resource/shell-study/0622-2013# ./test11.sh -h
- test11.sh - print number to different bases, 1.2 (stv '95)
- usage: test11.sh [number ...]
- If no number is given, the numbers are read from standard input.
- A number may be
- binary (base 2) starting with 0b (i.e. 0b1100)
- octal (base 8) starting with 0 (i.e. 014)
- hexadecimal (base 16) starting with 0x (i.e. 0xc)
- decimal otherwise (i.e. 12)
- root@ubuntu:~/resource/shell-study/0622-2013# ./test11.sh
- 6
- hex=6 dec=6 oct=6 bin=110
- 15
- hex=F dec=15 oct=17 bin=1111
- D
- hex=D dec=13 oct=15 bin=1101
- ^C
- root@ubuntu:~/resource/shell-study/0622-2013#
- variable=`bc << LIMIT_STRING
- options
- statements
- operations
- LIMIT_STRING
- `
- ...or...
- variable=$(bc << LIMIT_STRING
- options
- statements
- operations
- LIMIT_STRING
- )
接着看一个实例:使用"here document"来调用bc
- #!/bin/bash
- # 使用命令替换来调用'bc'
- # 并与'here document'相结合.
- var1=`bc << EOF
- 18.33 * 19.78
- EOF
- `
- echo $var1 # 362.56
- # 使用$( ... )这种标记法也可以.
- v1=23.53
- v2=17.881
- v3=83.501
- v4=171.63
- var2=$(bc << EOF
- scale = 4
- a = ( $v1 + $v2 )
- b = ( $v3 * $v4 )
- a * b + 15.35
- EOF
- )
- echo $var2 # 593487.8452
- var3=$(bc -l << EOF
- scale = 9
- s ( 1.7 )
- EOF
- )
- # 返回弧度为1.7的正弦.
- # "-l"选项将会调用'bc'算数库.
- echo $var3 # .991664810
- # 现在, 在函数中试一下...
- hyp= # 声明全局变量.
- hypotenuse () # 计算直角三角形的斜边.
- {
- hyp=$(bc -l << EOF
- scale = 9
- sqrt ( $1 * $1 + $2 * $2 )
- EOF
- )
- # 不幸的是, 不能从bash函数中返回浮点值.
- }
- hypotenuse 3.68 7.31
- echo "hypotenuse = $hyp" # 8.184039344
- exit 0
- root@ubuntu:~/resource/shell-study/0622-2013# chmod +x test12.sh
- root@ubuntu:~/resource/shell-study/0622-2013# ./test12.sh
- 362.56
- 593487.8452
- .991664810
- hypotenuse = 8.184039344
- root@ubuntu:~/resource/shell-study/0622-2013#
- #!/bin/bash
- # 这事实上是一个"Monte Carlo"蒙特卡洛模拟的非常简单的实例:
- #+ 蒙特卡洛模拟是一种由现实事件抽象出来的数学模型,
- #+ 由于要使用随机抽样统计来估算数学函数,所以使用伪随机数来模拟真正的随机数.
- # 想象有一个完美的正方形土地, 边长为10000个单位.
- # 在这块土地的中间有一个完美的圆形湖,
- #+ 这个湖的直径是10000个单位.
- # 这块土地的绝大多数面积都是水, 当然只有4个角上有一些土地.
- # (可以把这个湖想象成为这个正方形的内接圆.)
- #
- # 我们将使用老式的大炮和铁炮弹
- #+ 向这块正方形的土地上开炮.
- # 所有的炮弹都会击中这块正方形土地的某个地方.
- #+ 或者是打到湖上, 或者是打到4个角的土地上.
- # 因为这个湖占据了这个区域大部分地方,
- #+ 所以大部分的炮弹都会"扑通"一声落到水里.
- # 而只有很少的炮弹会"砰"的一声落到4个
- #+ 角的土地上.
- #
- # 如果我们发出的炮弹足够随机的落到这块正方形区域中的话,
- #+ 那么落到水里的炮弹与打出炮弹总数的比率,
- #+ 大概非常接近于PI/4.
- #
- # 原因是所有的炮弹事实上都
- #+ 打在了这个土地的右上角,
- #+ 也就是, 笛卡尔坐标系的第一象限.
- # (之前的解释只是一个简化.)
- #
- # 理论上来说, 如果打出的炮弹越多, 就越接近这个数字.
- # 然而, 对于shell 脚本来说一定会做些让步的,
- #+ 因为它肯定不能和那些内建就支持浮点运算的编译语言相比.
- # 当然就会降低精度.
- DIMENSION=10000 # 这块土地的边长.
- # 这也是所产生随机整数的上限.
- MAXSHOTS=10 # 开炮次数.
- # 10000或更多次的话, 效果应该更好, 但有点太浪费时间了.
- PMULTIPLIER=4.0 # 接近于PI的比例因子.
- get_random ()
- {
- SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }')
- RANDOM=$SEED # 来自于"seeding-random.sh"
- #+ 的例子脚本.
- let "rnum = $RANDOM % $DIMENSION" # 范围小于10000.
- echo $rnum
- }
- distance= # 声明全局变量.
- hypotenuse () # 从"alt-bc.sh"例子来的,
- { # 计算直角三角形的斜边的函数.
- distance=$(bc -l << EOF
- scale = 0
- sqrt ( $1 * $1 + $2 * $2 )
- EOF
- )
- # 设置 "scale" 为 0 , 好让结果四舍五入为整数值,
- #+ 这也是这个脚本中必须折中的一个地方.
- # 不幸的是, 这将降低模拟的精度.
- }
- # 初始化变量.
- shots=0
- splashes=0
- thuds=0
- Pi=0
- while [ "$shots" -lt "$MAXSHOTS" ] # 主循环.
- do
- xCoord=$(get_random) # 取得随机的 X 与 Y 坐标.
- yCoord=$(get_random)
- hypotenuse $xCoord $yCoord # 直角三角形斜边 =
- #+ distance.
- ((shots++))
- printf "#%4d " $shots
- printf "Xc = %4d " $xCoord
- printf "Yc = %4d " $yCoord
- printf "Distance = %5d " $distance # 到湖中心的
- #+ 距离 --
- # 起始坐标点 --
- #+ (0,0).
- if [ "$distance" -le "$DIMENSION" ]
- then
- echo -n "SPLASH! "
- ((splashes++))
- else
- echo -n "THUD! "
- ((thuds++))
- fi
- Pi=$(echo "scale=9; $PMULTIPLIER*$splashes/$shots" | bc)
- # 将比例乘以4.0.
- echo -n "PI ~ $Pi"
- echo
- done
- echo
- echo "After $shots shots, PI looks like approximately $Pi."
- # 如果不太准的话, 那么就提高一下运行的次数. . .
- # 可能是由于运行错误和随机数随机程度不高造成的.
- exit 0
- root@ubuntu:~/resource/shell-study/0622-2013# ./test13.sh
- # 1 Xc = 9684 Yc = 9797 Distance = 13775 THUD! PI ~ 0
- # 2 Xc = 8460 Yc = 6413 Distance = 10615 THUD! PI ~ 0
- # 3 Xc = 1111 Yc = 1189 Distance = 1627 SPLASH! PI ~ 1.333333333
- # 4 Xc = 4230 Yc = 4986 Distance = 6538 SPLASH! PI ~ 2.000000000
- # 5 Xc = 9403 Yc = 58 Distance = 9403 SPLASH! PI ~ 2.400000000
- # 6 Xc = 9345 Yc = 5325 Distance = 10755 THUD! PI ~ 2.000000000
- # 7 Xc = 6826 Yc = 4682 Distance = 8277 SPLASH! PI ~ 2.285714285
- # 8 Xc = 3762 Yc = 4721 Distance = 6036 SPLASH! PI ~ 2.500000000
- # 9 Xc = 7446 Yc = 5189 Distance = 9075 SPLASH! PI ~ 2.666666666
- # 10 Xc = 1337 Yc = 9442 Distance = 9536 SPLASH! PI ~ 2.800000000
- After 10 shots, PI looks like approximately 2.800000000.
- root@ubuntu:~/resource/shell-study/0622-2013#
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输入, 但是, 它却有特定的用途.
一个实例:将10进制数字转换为16进制数字
- #!/bin/bash
- # hexconvert.sh: 将10进制数字转换为16进制数字.
- E_NOARGS=65 # 缺少命令行参数错误.
- BASE=16 # 16进制.
- if [ -z "$1" ]
- then
- echo "Usage: $0 number"
- exit $E_NOARGS
- # 需要一个命令行参数.
- fi
- # 练习: 添加命令行参数检查.
- hexcvt ()
- {
- if [ -z "$1" ]
- then
- echo 0
- return # 如果没有参数传递到这个函数中的话就"return" 0.
- fi
- echo ""$1" "$BASE" o p" | dc
- # "o" 设置输出的基数(数制).
- # "p" 打印栈顶.
- # 参考dc的man页来了解其他的选项.
- return
- }
- hexcvt "$1"
- exit 0
- root@ubuntu:~/resource/shell-study/0622-2013# chmod +x test14.sh
- root@ubuntu:~/resource/shell-study/0622-2013# ./test14.sh 15
- F
- root@ubuntu:~/resource/shell-study/0622-2013# ./test14.sh 6
- 6
- root@ubuntu:~/resource/shell-study/0622-2013# ./test14.sh 20
- 14
- root@ubuntu:~/resource/shell-study/0622-2013#
在脚本中使用浮点运算的另一种方法是使用awk内建的数学运算函数
看一个用awk的实例:计算直角三角形的斜边
- #!/bin/bash
- # hypotenuse.sh: 返回直角三角形的斜边.
- # (直角边长的平方和,然后对和取平方根)
- ARGS=2 # 需要将2个直角边作为参数传递进来.
- E_BADARGS=65 # 错误的参数值.
- if [ $# -ne "$ARGS" ] # 测试传递到脚本中的参数值.
- then
- echo "Usage: `basename $0` side_1 side_2"
- exit $E_BADARGS
- fi
- AWKSCRIPT=' { printf( "%3.7f\n", sqrt($1*$1 + $2*$2) ) } '
- # 命令 / 传递给awk的参数
- # 现在, 将参数通过管道传递给awk.
- echo -n "Hypotenuse of $1 and $2 = "
- echo $1 $2 | awk "$AWKSCRIPT"
- exit 0
- root@ubuntu:~/resource/shell-study/0622-2013# ./test16.sh
- Usage: test16.sh side_1 side_2
- root@ubuntu:~/resource/shell-study/0622-2013# ./test16.sh 4 6
- Hypotenuse of 4 and