需求分析
shell脚本开发存在的一个问题是各种不一致的数据格式。规范数据格式的难度可小可大。数据格式算是其中最有挑战性的工作之一,这是因为指定日期的方法各种各样。哪怕是提示过特定的格式,例如按照“月-日-年”,照样有可能得到不一致的输入:月份没有采用数字,而是用了月份名称或月份名称缩写,甚至还有全部是大写字母的月份全称。有鉴于此,一个能够规范日期的函数,尽管本身很基础,却能在后续的脚本编写工作中帮上大忙。
代码实现
以下代码可以对符合一组简单要求的日期进行规范:月份要么采用名称,要么采用1至12之间的值;年份必须采用4位数的值。规范后的日期由月份名称(3个字母的缩写)、天数以及4位数的年份组成。
#!/bin/bash
# filename:normdate
# function:将月份规范成3个字母,首字母大写
# description:该脚本随后将作为脚本7的辅助函数。如果没有错误,那么以0值退出。
monthNumToName(){
# 将变量month设置为相应的值
case $1 in
1) month="Jan" ;; 2) month="Feb" ;;
3) month="Mar" ;; 4) month="Apr" ;;
5) month="May" ;; 6) month="Jun" ;;
7) month="Jul" ;; 8) month="Aug" ;;
9) month="Sep" ;; 10) month="Oct" ;;
11) month="Nov" ;; 12) month="Dec" ;;
*) echo "$0:Unknown numeric month value $1" >&2 exit
esac
return 0
}
# 主脚本开始:如果要将该脚本包含到其他脚本之内,那么删除或注释掉本行以下的所有内容。
# 输入验证
if [ $# -ne 3 ];then
echo "Usage: $0 month day year" >&2
echo "Formats are August 3 1962 and 8 3 1962" >&2
exit 1
fi
if [ $3 -le 99 ];then
echo "$0: expected 4-digit year value." >&2
exit 1
fi
# 输入的月份是否为数字
if [ -z $(echo $1 | sed 's/[[:digit:]]//g') ];then
monthNumToName $1
else
# 规范前3个字母,首字母大写,其余小写。
month="$(echo $1 | cut -c1 | tr '[:lower:]' '[:upper:]')"
month="$month$(echo $1 | cut -c2-3 | tr '[:upper:]' '[:lower:]')"
fi
echo $month $2 $3
exit 0
运行脚本
注意脚本中的[ -z $(echo $1 | sed 's/[[:digit:]]//g') ]
。它移除了第一个输入字段中的所有数字,然后使用-z检测结果是否为空。如果为空,则意味着该字段中只有数字,可以使用函数monthNumToName将其直接映射成月份名称,该函数还会验证数字代表的月份是否有效。
否则,我们假定字段中包含的是月份字符串,这得借助两个子shell,通过复杂的cut和tr管道来实现规范化。
- 第一个子shell:
$(echo $1 | cut -c1 | tr '[:lower:]' '[:upper:]')
只提取出输入的首个字符,然后使用tr将其转换成大写(命令序列echo $1|cut -c1也可以写成POSIX形式的${1%${1#?}})。 - 第二个子shell:
$(echo $1 | cut -c2-3 | tr '[:upper:]' '[:lower:]')
提取出后两个字符并将其转换成小写,这样就生成了首字母大写的月份缩写(长度为3个字符)。
注意:这种字符串操作方法并没有检查输入的月份是否有效,这一点和月份为数字时的处理并不一样。
为了确保以后引入了normdate功能的脚本拥有最大的灵活性,这个脚本被设计成以3个命令行参数的形式接收输入。如果你只打算将其用于交互模式,那就得提示用户输入这些参数,不过这样一来,要想在其他脚本中调用normdate就更困难了。
$ sh normdate.sh 8 3 62
# -> normdate: expected 4-digit year value.
$ sh normdate.sh 8 3 1962
# -> Aug 3 1962
$ sh normdate.sh AUGUST 03 1962
# -> Aug 03 1962
注意:该脚本只能规范月份描述,天数格式(例如包含前导数字0的天数)和年份保持不变。
精益求精
在你兴奋于能够为该脚本添加大量扩展,使其变得更为复杂之前,先看看脚本07,它用到了normdate来验证输入日期。
可以做出的一处改动是允许脚本接受形如MM/DD/YYYY或MM-DD-YYYY的日期格式,将下面的代码添加到第一个条件语句之前。
if [ $# -eq 1 ];then
set -- $(echo $1 | sed 's/[\/\-]/ /g')
fi
修改完之后,就可以输入并规范下列常见格式了:
$ sh normdate.sh 6-10-2000 #-> Jun 10 2000
$ sh normdate.sh March-11-1911 #-> Mar 11 1911
$ sh normdate.sh 8/3/1962 #-> Aug 3 1962
如果你仔细阅读代码,会意识到还可以采用更复杂的改进方法,使其能够验证指定日期中的年份以及各种国际日期格式。这些就作为练习吧!