第一章:The Missing Code Library--4.优雅的表示大数

   程序员经常犯的一个错误是:在向用户显示计算的结果时,并没有在第一时间格式化好它们。如果用户没有从右向左手动计数,然后在心里每三个数字就插入一个逗号的话,是很难界定43245435这个数字有没有达到百万的(英文中的数字记法,如果是汉语的话,个人觉得还是4个数字插入一个逗号更好读点,如果汉语需要这样计数的话)。使用下面这个脚本来格式化你的计算结果。

nicenumber.sh
 1 #!/bin/sh
 2 # nicenumber.sh -- 给定一个数字,将它用逗号分隔的形式表示出来
 3 # 以DD和TD作为实例。实例化nicenumber.sh,如果指定了第二个参数,会产生一个标准输出。
 4 
 5 nicenumber()
 6 {
 7     # 注意,在输入中假定点号是十进制的小数位。
 8     # 输出中的十进制分隔符是点号,除非用户使用了 -d 选项。
 9 
10     integer=$(echo $1 | cut -d. -f1)         # 小数点左边
11     decimal=$(echo $1 | cut -d. -f2)        # 小数点右边
12 
13     if [ $decimal != $1 ]; then
14         # 这里有个小数部分,将它包含进去
15         result="${DD:="."}$decimal"
16     fi
17 
18     thousands=$integer
19 
20     while [ $thousands -gt 999 ]; do
21         remainder=$(($thousands%1000))         # 3个最低有效位数字
22 
23         while [ ${#remainder} -lt 3 ]; do     # 如果需要,强制以0打头
24             remainder="0$remainder"
25         done
26 
27         thousands=$(($thousands/1000))         # remainder左侧数字,如果有
28         result="${TD:=","}${remainder}${result}"     # 从右向左生成
29     done
30 
31     nicenum="${thousands}${result}"
32     if [ ! -z $2 ]; then
33         echo $nicenum
34     fi
35 }
36 
37 DD="."     # 十进制的点分隔符,区分整数部分和小数部分
38 TD=","     # 千位分隔符,每三个数字添加一个
39 
40 while getopts "d:t:" opt; do     # 下面会详谈getopts的用法,它很特殊,但很强力
41     case $opt in
42         d)DD="$OPTARG";;
43         t)TD="$OPTARG";;
44     esac
45 done
46 shift $(($OPTIND-1))
47 
48 if [ $# -eq 0 ]; then
49     echo "Usage: $(basename $0)[-d c] numeric value"
50     echo " -d 指定十进制的小数位分隔符(默认是点号)"
51     echo " -t 指定了千位分隔符(默认是逗号)"
52 fi
53 
54 nicenumber $1 1     # 第二个参数强制 nicenumber 输出到标准输出
55 
56 exit 0

   脚本如何工作:
   这个脚本的核心是nicenumber函数中的while循环。该函数获得数值,然后循环将它分割为3个最低有效位数字(也就是会出现在下一个逗号右边的3个数字),以及保留的数值。接着,这些最低有效位又会通过循还进行处理。

   运行代码:
   要运行这个脚本,只需简单指定一个非常大的数值,然后脚本就会根据需要,要么使用默认的要么按照标志位指定(-d或-t)的,来增加小数分隔符和千位分隔符。因为函数的输出是一个数字,所以结果可以像下面这样:echo "Do you really want pay $(nicenumber $price) dollars?"

   运行结果:

View Code
 1 ./nicenumber.sh 5894625
 2 5,894,625
 3 ./nicenumber.sh 589462532.433
 4 589,462,532.433
 5 ./nicenumber.sh -d, -t. 589462532.433
 6 589.462.532,433
 7 ./newnicenum.sh 
 8 Usage: newnicenum.sh [-d c] [-t c] numeric value
 9  -d 指定十进制的小数位分隔符(默认是点号)
10  -t 指定了千位分隔符(默认是逗号)

   延伸阅读:
   不同的国家使用的小数位分隔符和千位分隔符是不同的,因此需要增加标志位来灵活的指定。比如,德国和意大利会使用-d "."和-t ",",法国会用-d ","和-t " ",而瑞士有4种语言,他们会用-d "."和-t ""。这个例子很好的展示了灵活应用是如何优于写死代码的,所以这个脚本工具完全可以适用于大多数国家的用户。另一方面,该脚本中把输入中的小数位分隔符写死为点号,如果你预计输入中的小数位使用不同的分隔符,你可以把两个调用cut命令中的指定分隔符给改变。看下面:

integer=$(echo $1 | cut "-d$DD" -f1)      #小数位左侧
decimal=$(echo $1 | cut "-d$DD" -f2)      #小数位右侧

   这样可以运行,但如果使用一个完全不同的小数分隔符的话,就不那么完美了。一个更加精妙的解决方法是在这2行代码前面先测试下,以此来确保预期的小数位分隔符是用户所要求的。我们可以学习第2个脚本中的思想,用sed删除所有数字,看看剩下的是什么:

1 seperator="$(echo $1 | sed 's/[[:digit:]]//g')"
2 if [ !-z "$seperator" -a "$seperator" != "$DD"]; then
3   echo "$0: Unknown decimal seperator $seperator encounted." >&2
4   exit 1
5 fi

个人心得:

   1.cut中的分隔符是-d选项,-f是指的域。想想awk。这个就是简化版。
   2.在Shell脚本中对于字符串的引用--引号和大括号。
   3.getopts:分析传递到脚本中的命令行参数的最强力工具

getopts具有以下特点:
   1.所有传递到脚本中的参数,前面必须加上一个减号【-】,getopts是不会处理不带-前缀的参数的。
   2.getopts一般放在一个while循环中,而这个while循环和标准的while循环有些不同,它是没有中括号[]判断的。
   3.getopts将会取代外部命令getopt
在本脚本中使用的getopts结构的说明:
d和t都被认为是标志选项,d后面跟一个冒号,说明该选项要带一个参数,同理t。

shift $(($OPTIND-1))

上句的作用是参数指针向下移动一位。shift是可以带参数的,参数是移动的个数。将参数指针OPTIND减1,就是指向下一个参数的意思。这时候,$1指向第一个非选项参数了。
如何理解它,看我做个测试:

1 ./newnicenum.sh -d. -t 589462532.345
2 然后打印
3 Usage:  newnicenum.sh  [-d c] [-t c] numeric value4  -d 指定十进制的小数位分隔符(默认是点号)
5  -t 指定了千位分隔符(默认是逗号)"

为什么?因为 -t 后面没有跟上一个参数,还记得getopts中 t 后面的冒号吗?所以589462532.345被认为是t的参数,然后$1指向为空了,因为这个数字参数被shift掉了。所以$#(即参数个数)等于0了,打印3条语句。

再测试下:

1 ./newnicenum.sh -d -t, 589462532.345
2 然后打印
3 ./newnicenum.sh: Unknown decimal seperator . encounted.

为什么不打印那3句话了?因为它认为小数位分隔符是点号,而$DD是为空的($DD是在getopts中赋值的,为空),它俩不同,所以符合nicenumber函数中的开头的if判断语句中-a后面的条件。

注:以上关于getopts的说明参考了"Advanced Bash-Scripting Guide"。

汇总下延伸中的内容,最终脚本如下:

 1 #!/bin/sh
 2 
 3 nicenumber()
 4 {
 5     # 注意,在输入中假定点号是十进制的小数位。
 6     # 输出中的十进制分隔符是点号,除非用户使用了 -d 选项。
 7 
 8     seperator="$(echo $1 | sed 's/[[:digit:]]//g')"
 9     if [ ! -z "$seperator" -a "$seperator" != "$DD" ]; then
10         echo "$0: Unknown decimal seperator $seperator encounted." >&2
11         exit 1
12     fi
13 
14     integer=$(echo $1 | cut "-d$DD" -f1)     #小数位左侧
15     decimal=$(echo $1 | cut "-d$DD" -f2)    #小数位右侧
16 
17     if [ $decimal != $1 ]; then
18         # 这里有个小数部分,将它包含进去
19         result="${DD:="."}$decimal"
20     fi
21 
22     thousands=$integer
23 
24     while [ $thousands -gt 999 ]; do
25         remainder=$(($thousands%1000))         # 3个最低有效位数字
26 
27         while [ ${#remainder} -lt 3 ]; do     # 如果需要,强制以0打头
28             remainder="0$remainder"
29         done
30 
31         thousands=$(($thousands/1000))         # remainder左侧数字,如果有
32         result="${TD:=","}${remainder}${result}"  # 从右向左生成,:=的用法,是如果TD还没有设置,就将它的默认值设置为逗号
33     done
34 
35     nicenum="${thousands}${result}"
36     if [ ! -z $2 ]; then
37         echo $nicenum
38     fi
39 }
40 
41 DD="."     # 十进制的点分隔符,区分整数部分和小数部分
42 TD=","     # 千位分隔符,每三个数字添加一个
43 
44 while getopts "d:t:" opt; do     # 下面会详谈getopts的用法,它很特殊,但很强力
45     case $opt in
46         d)DD="$OPTARG";;
47         t)TD="$OPTARG";;
48     esac
49 done
50 shift $(($OPTIND-1))
51 
52 if [ $# -eq 0 ]; then
53     echo "Usage: " $(basename $0) " [-d c] [-t c] numeric value"
54     echo " -d 指定十进制的小数位分隔符(默认是点号)"
55     echo " -t 指定了千位分隔符(默认是逗号)"
56 fi
57 
58 nicenumber $1 1     # 第二个参数强制 nicenumber 输出到标准输出
59 
60 exit 0

 

 

 

转载于:https://www.cnblogs.com/lalala23456/archive/2012/12/03/2799709.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值