一、问题
实际工作中,经常会碰到对数值的检测,在此将检测整数、浮点数和日期的合法性综合在一起,总结各种不同的检测方法。特别是日期总结了shell处理日期非常好的方法,可以借鉴使用,每个函数都可以根据需要独立提取出来。
二、详解
(1)检测输入整数的合法性
正负数的判断,范围的判断。可为负数,参数可为1~3个(后两个表范围)。
#!/bin/bash
validint()
{
signed="" #初始化
integer=$1 min=$2 max=$3
#判断是否为负数,第一个字符是否为-。如是,则判断后面的是否为整数;若非负,则判断是否为整数。
if [ "$(echo $1 | cut -c1)" = "-" ] ; then
signed="-"
integer="${integer#?}" #${variable#pattern}把variable中的内容去掉左边最短的匹配模式,?表示仅与一个任意字符匹配
fi
if [ -z "$integer" ]; then #只有一个符号没有数字是非法的
echo "Invalid input, just a '-' is not allowed" >&2 #注意:>&2 三个字符紧连。
return 1
fi
#判断是否都是数字组成。
if [ -n "$(echo $integer | sed 's/[[:digit:]]//g')" ] ; then #判断数字的常用方法
echo "Invalid integer, it includes some char but digit" >&2
return 1
fi
integer="$signed$integer"
#范围判断,当min和max为空时(即未传范围),默认为$integer。
if [ $integer -lt ${min:=$integer} ] ;then #:=代表若min值为空则赋值成默认的integer,若min已被赋值则为不再以integer赋值。等价于[ -z $min -a min=$integer ];if [ $integer -lt min ] ; then。
echo "$integer is too small,it should greater than $min" >&2
return 1
fi
if [ $integer -gt ${max:=$integer} ] ;then
#等价于[ -z $max -a max=$integer ];if [ $integer -gt max ] ; then。
echo "$integer is too large,it should little than $max" >&2
return 1
fi
return 0
}
####函数入口####
#判断传参的合法性
if [ $# -eq 0 ]
then
echo "you could not do nothing" >&2
exit 1
fi
if validint "$1" "$2" "$3" #三个参数,$2和$3表范围可以缺省
then
echo "your input integer is valid"
fi
(2)检测输入浮点数的合法性
思路:首先根据小数点判断是整数还是浮点数,无小数点作整数判断处理;有小数点使用cut将浮点数分成两部分,前半部分为合法整数并且后半部分为>=0的整数才得出浮点数合法的结论。例如-1.2、1.23、-.23都是合法的浮点数。
#检测浮点数的合法性,不支持科学表示法
#!/bin/bash
validfloat()
{
fvalue=$1
if [ -z "$fvalue" ] ; then # 保证传入参数非空
echo "you input nothing" >&2
return 1
fi
#判断有点否(判断方法是非.号替换法)
if [ -n "$(echo $fvalue | sed 's/[^.]//g')" ]; then
integer="$(echo $fvalue | cut -d. -f1)"
decimal="$(echo $fvalue | cut -d. -f2)"
#判断整数部分的合法性,当-.75等为合法
if [ "$integer" != "-" ] ; then
if ! validint "$integer" ; then
echo "part before dot $integer is not valide!" >&2
return 1
fi
fi
#判断小数部分整数的合法性。必须>=0, 当为空时不判断。
if [ -n "$decimal" ]; then
if ! validint "$decimal" 0; then
echo "part after dot $decimal is not valide!" >&2
return 1
fi
fi
else #没有小数点,就当作整数处理
if validint "$fvalue"; then
echo "your input is a integer" >&2
return 1
fi
fi
}
#此函数在判断整数时已完成
validint()
{
signed="" #初始化
integer=$1 min=$2 max=$3
#判断是否为负数,第一个字符是否为-。如是,则判断后面的是否为整数;若非负,则判断是否为整数。
if [ "$(echo $1 | cut -c1)" = "-" ] ; then
signed="-"
integer="${integer#?}" #${variable#pattern}把variable中的内容去掉左边最短的匹配模式,?表示仅与一个任意字符匹配
fi
if [ -z "$integer" ]; then #只有一个符号没有数字是非法的
echo "Invalid input, just a '-' is not allowed" >&2 #注意:>&2 三个字符紧连。
return 1
fi
#判断是否都是数字组成。
if [ -n "$(echo $integer | sed 's/[[:digit:]]//g')" ] ; then #判断数字的常用方法
echo "Invalid integer, it includes some char but digit" >&2
return 1
fi
integer="$signed$integer"
#范围判断,当min和max为空时(即未传范围),默认为$integer。
if [ $integer -lt ${min:=$integer} ] ;then #:=代表若min值为空则赋值成默认的integer,若min已被赋值则为不再以integer赋值。等价于[ -z $min -a min=$integer ];if [ $integer -lt min ] ; then。
echo "$integer is too small,it should greater than $min" >&2
return 1
fi
if [ $integer -gt ${max:=$integer} ] ;then
#等价于[ -z $max -a max=$integer ];if [ $integer -gt max ] ; then。
echo "$integer is too large,it should little than $max" >&2
return 1
fi
return 0
}
#####函数入口#####
if validfloat $1; then #判断浮点数
echo "$1 is a valid floating-point value"
fi
exit 0
(3)检测输入日期的合法性
日期中包括五部分内容,检测日期合法性、判断润年、获取当月天数、输出昨天日期和输出明天日期。支持日期的多种输入格式。#!/bin/sh
######################################
#SHELL日期计算函数 #
#1:判断是否闰年check_leap() #
#2:获取月份最大日期get_mon_days() #
#3:检查日期格式check_date() #
#4:返回昨天日期get_before_date() #
#5:返回明天日期get_next_date() #
######################################
#-----------------------------------------------------------------
#判断是否闰年
#input:year
#output: "true" "fase"
check_leap()
{
Y=`expr substr $1 1 4`
r1=`expr $Y \% 4` #if((year%4==0&&year%100!=0)||year%400==0)
r2=`expr $Y \% 100` #(1)普通年能被4整除且不能被100整除的为闰年,(2)世纪年能被400整除的是闰年。
r3=`expr $Y \% 400`
if [ "$r1" -eq 0 -a "$r2" -ne 0 -o "$r3" -eq 0 ];then
FRUN="true"
else
FRUN="false"
fi
echo $FRUN
}
#-----------------------------------------------------------------
# 获取月份最大日期
#方法1
get_mon_days()
{
Y=`expr substr $1 1 4`
M=`expr substr $1 5 2`
case "$M" in
01|03|05|07|08|10|12) days=31;;
04|06|09|11) days=30;;
02)
_tmpStr=`check_leap "$Y"` #判断是否闰年
if [ "$_tmpStr" = "true" ] ; then
#闰年
days=29
else
days=28
fi
;;
*)
days=0
;;
esac
echo $days
}
#-----------------------------------------------------------------
# 获取月份最大日期
#方法2
get_mon_days2()
{
Y=`expr substr $1 1 4` #取4位数表示年
M=`expr substr $1 5 2` #取2位数表示月
#取当月底最后一天
aa=`cal $M $Y` #日历 #cal命令
days=`echo $aa | awk '{print $NF}'` #awk '{print $NF}'输出每行最后一个域的内容,区别于awk '{print NF}'空格分割的域数量
echo $days
}
#检查日期格式(例:20141022)
#返回状态($?) 0 合法 1 非法
check_date()
{
[ $# -ne 1 ] && return 1 #检查是否传入一个参数
_lenStr=`expr length "$1"` #检查字符串长度
[ "$_lenStr" -ne 8 ] && return 1
#检查是否输入的是非0开头的数字
_tmpStr=`echo "$1" | grep "^[^0][0-9]*$"`
[ -z "$_tmpStr" ] && return 1
Y=`expr substr $1 1 4`
M=`expr substr $1 5 2`
D=`expr substr $1 7 2`
#检查月份
[ "$M" -lt 1 -o "$M" -gt 12 ] && return 1
#取当月天数
days=`get_mon_days "$Y$M"`
#检查日
[ "$D" -lt 1 -o "$D" -gt "$days" ] && return 1
return 0
}
#-----------------------------------------------------------------
#返回昨天日期
get_before_date()
{
Y=`expr substr $1 1 4`
M=`expr substr $1 5 2`
D=`expr substr $1 7 2`
#某月01日的情况
if [ "$D" -eq 01 ]
then
if [ "$M" -eq 01 ]
then
#某年01月01日的情况
#取上年年底日期(12月31日)
YY=`expr $Y - 1`
be_date="${YY}1231"
else
#取上个月月末日期
MM=`expr $M - 1`
MM=`printf "%02d" $MM`
dad=`get_mon_days "$Y$MM"`
be_date="$Y$MM$dad"
fi
else
#通常情况
DD=`expr $D - 1`
DD=`printf "%02d" $DD`
be_date="$Y$M$DD"
fi
echo $be_date
}
#-----------------------------------------------------------------
#返回明天日期
get_next_date()
{
Y=`expr substr $1 1 4`
M=`expr substr $1 5 2`
D=`expr substr $1 7 2`
dad=`get_mon_days "$Y$M"` #当月天数
if [ "$D" = "$dad" ];then
#特殊情况:月底
if [ "$M$D" = "1231" ];then
#年底的情况
YY=`expr $Y + 1`
next_date="${YY}0101"
else
MM=`expr $M + 1`
MM=`printf "%02d" $MM`
next_date="$Y${MM}01"
fi
else
#通常情况
DD=`expr $D + 1`
DD=`printf "%02d" $DD`
next_date="$Y$M$DD"
fi
echo $next_date
}
#----------------------------main入口-------------------------------------
if [ $# -ne 1 ]
then
echo "<ERROR>Usage:`basename $0` year-month-day or year/month/day or yearmonthday"
exit 1
fi
echo "日期:$1"
set -- $(echo $1 | sed 's/[\/\-]/ /g') #处理MM/DD/YYYY or MM-DD-YYYY 的形式,set 的--命令可以将其后的参数赋予位置参数$1、$2和$3
#检查日期格式
_dateStr="$1$2$3"
check_date $_dateStr
if [ $? -eq 1 ];then
echo "<ERROR>输入日期[$1]格式错误!示例:(`date +%Y%m%d`)"
exit 1
fi
cat <<EOF
是否闰年: `check_leap $_dateStr`
当月天数: `get_mon_days2 $_dateStr`
昨天日期: `get_before_date $_dateStr`
明天日期: `get_next_date $_dateStr`
EOF
三、总结
(1)数字、日期和字符串的处理都是很常见的问题,字符串输入为字母数字的判断在第七章中已介绍。
(2)若只是判断日期的合法性,则可抽取部分代码,判断年、月的天数和日的范围即可。
(3)本代码若有不完善的地方,可请大家留言,也可联系本人yang.ao@i-soft.com.cn。