在shell脚本中验证日期格式

18

我需要创建一个Shell脚本,在其中一个参数将是格式为dd/mm/yyyy的日期。我的问题是,如何检查传递的日期参数是否真的遵循这个日期格式?我尝试使用以下grep命令:

if echo "$1" | grep -q '^[0-3][0-9]/[0-1][0-9]/[0-9]\{4\}$'

但它没有给出正确的格式,因为日期例如可以是33、34、(...),这并不是真正的正确格式。有人知道能够真正检查传递的日期是否遵循dd/mm/yyyy格式的方法吗?


请参见http://unix.stackexchange.com/a/236374/106927。 - Dmitry Grigoryev
14个回答

30

使用date

date "+%d/%m/%Y" -d "09/99/2013" > /dev/null  2>&1
 is_valid=$?

日期字符串必须以“MM/DD/YYYY”格式表示。

如果不为0,则日期格式无效。


1
谢谢你的回答。这真的有效,但只适用于格式mm/dd/yyyy。如果你在终端上检查选项-d,它只检查格式mm/dd/yyyy。我尝试更改格式,但它不会影响日期命令使用选项-d的方式。 - Jairo Franchi
19
这个问题是“如何检查传递的日期参数是否真正遵循这个日期格式”,但是现有的代码并没有做到这一点。因为“date”命令可以接受不同格式的日期,例如“tomorrow”和“date -d 'tomorrow + 5 months 20:00:01'”都是有效的格式。 - Aleks-Daniel Jakimenko-A.
同时,对于我来说,“date -d '05/20/1900'”也会抛出错误。这也取决于您的架构(64位系统的日期范围可能更大)。这绝不是正确的方法。请改用konsolebox的答案 - Aleks-Daniel Jakimenko-A.
1
@Aleks-DanielJakimenko 第二个答案同样糟糕,接受像33/19/2000这样的日期。 - Dmitry Grigoryev
10
根据Aleks-Daniel Jakimenko-A.的指出,这段代码实际上并没有检查日期格式。为了验证dd是否是YYYY-MM-DD格式且为有效日期,可以执行以下操作:[[ $(date "+%Y-%m-%d" -d "$dd") == "$dd" ]] || echo 'invalid date format' - tkokoszka
显示剩余4条评论

19

最简单的解决方案,仍然完美运作,如下:

if [[ $1 =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] && date -d "$1" >/dev/null 2>&1
   ...

它的含义是将两个检查组合在一起:

  • 第一部分检查$1是否符合此格式:NNNN-NN-NN
  • 第二部分检查它是否是一个有效的日期

你需要这两个检查,因为:

  • 如果你不进行第一项检查,即使您的变量在另一种格式中是一个有效的日期,date也会退出并返回0
  • 如果您不进行第二项检查,则可能会得到0,即使变量是像2016-13-45这样的日期

如果您不使用破折号,第二个检查将成功。例如,20203112将在日期-d检查中成功。 - thanos.a

6

这个函数需要两个字符串输入:一个格式字符串和一个日期字符串。

格式字符串使用date命令中的代码,但不包括“+”符号。

如果提供的日期与给定的格式匹配,则该函数返回0,否则返回1。

代码(my_script.sh)

#!/bin/bash

datecheck() {
    local format="$1" d="$2"
    [[ "$(date "+$format" -d "$d" 2>/dev/null)" == "$d" ]]
}

date_test="$1"

echo $date_test
if datecheck "%d %b %Y" "$date_test"; then
    echo OK
else
    echo KO
fi

输出

$ ./my_script.sh "05 Apr 2020"
05 Apr 2020
OK
$ ./my_script.sh "foo bar"
foo bar
KO

这是一个非常棒的答案! - Steve-B

5

首先,使用正则表达式检查输入的格式。然后使用awk切换到mm/dd/yyyy格式,并使用date进行验证。您可以在if语句中使用以下表达式:

echo "$1" | egrep -q '^[0-3][0-9]/[0-1][0-9]/[0-9]{4}$' && date -d "$(echo "$1" | awk 'BEGIN{FS=OFS="/"}{print $2"/"$1"/"$3}')" >/dev/null 2>&1

赞赏你实际回答了OP的问题,并且只用了一行代码。egrep正则表达式可以简化为'^[0-9]{2}/[0-9]{2}/[0-9]{4}$',因为后续使用date检查将会淘汰像40/01/2020这样的日期。 - Joman68

3

Bash中最简单的获取dd/mm/yyyy日期格式的方法如下:

if [[ $1 == [0-3][0-9]/[0-1][0-9]/[0-9][0-9][0-9][0-9] ]]

或者

if [[ $1 =~ ^[0-3][0-9]/[0-1][0-9]/[0-9]{4}$ ]]

这正是原帖所说的不起作用的地方,因为它接受无效的日期,如33/19/2000。 - augurar
@augurar 是的,我本来可以做得更准确一些。但是再说了,即使是被接受的解决方案也可以接受其他格式,而不仅仅是“dd/mm/yyyy”。所以我认为现在这并不重要。 - konsolebox

2
#! /bin/bash

isDateInvalid()
{
    DATE="${1}"

    # Autorized separator char ['space', '/', '.', '_', '-']
    SEPAR="([ \/._-])?"

    # Date format day[01..31], month[01,03,05,07,08,10,12], year[1900..2099]
    DATE_1="((([123][0]|[012][1-9])|3[1])${SEPAR}(0[13578]|1[02])${SEPAR}(19|20)[0-9][0-9])"

    # Date format day[01..30], month[04,06,09,11], year[1900..2099]
    DATE_2="(([123][0]|[012][1-9])${SEPAR}(0[469]|11)${SEPAR}(19|20)[0-9][0-9])"

    # Date format day[01..28], month[02], year[1900..2099]
    DATE_3="(([12][0]|[01][1-9]|2[1-8])${SEPAR}02${SEPAR}(19|20)[0-9][0-9])"

    # Date format day[29], month[02], year[1904..2096]
    DATE_4="(29${SEPAR}02${SEPAR}(19|20(0[48]|[2468][048]|[13579][26])))"

    # Match the date in the Regex

    if ! [[ "${DATE}" =~ "^(${DATE_1}|${DATE_2}|${DATE_3}|${DATE_4})$" ]]
    then
        echo -e "ERROR - '${DATE}' invalid!"
    else
        echo "${DATE} is valid"
    fi
}

echo
echo "Exp 1: "`isDateInvalid '12/13/3000'`
echo "Exp 2: "`isDateInvalid '12/11/2014'`
echo "Exp 3: "`isDateInvalid '12 01 2000'`
echo "Exp 4: "`isDateInvalid '28-02-2014'`
echo "Exp 5: "`isDateInvalid '12_02_2002'` 
echo "Exp 6: "`isDateInvalid '12.10.2099'`
echo "Exp 7: "`isDateInvalid '31/11/2000'`

2
如何使用 awk:
echo "31/12/1999" | awk  -F '/' '{ print ($1 <= 31 && $2 <= 12 && match($3, /^[1-9][1-9][1-9][1-9]$/)) ? "good" : "bad" }'

如果它是有效日期,则打印“good”,否则打印“bad”。

这将验证像31/02/201229/02/200131/04/2000等日期... :( - gniourf_gniourf
我需要验证多个文件中的多个日期格式,我已经使用awk获取了日期。这个解决方案比KonsoleBox或PxL的更适合请求。话虽如此,请求中日期的格式与此解决方案不同。应该是 echo "31/12/1999" | awk -F '/' '{ print ($1 <= 12 && $2 <= 31 && match($3, /^[1-9][1-9][1-9][1-9]$/)) ? "good" : "bad" }' 唯一的注意点是,即使这不是一个日期,02/30/2014也会通过,但这仍然完全符合我的需求。 - JNevill
对于年份参数,最好使用[0-9]而不是[1-9]。 - Mauricio Paz

1
我愿意为稍微不同的格式提供一个扩展答案,但是可以很容易地将其更改为已给出的dd/mm/YY格式;它在busybox(posix shell)上进行了测试。
这是与“busybox posix shell脚本日期”和“测试格式”或“验证”等类似网页搜索相似度最高的结果之一,因此这里提供我的解决方案(在1.29.3、1.23.1版本的busybox上进行了测试)。
#!/bin/sh

##########
#
# check if date valid in busybox
#   tested in busybox 1.29.3, 1.23.1
#
# call with:
#   $0 <yyyymmdd>
#
##########

mydate=$1

if echo $mydate | grep -qE '20[0-9][0-9](0[1-9]|1[0-2])([012][0-9]|3[01])'; then
    printf 'may be valid\n'

    date +%Y%m%d -d $mydate -D %Y%m%d > /dev/null  2>&1
    is_valid=$?
    if [ $is_valid -ne 0 ]; then
        printf 'not valid\n'
        return 1
    else
        mytestdate=$(date +%Y%m%d -d $mydate -D %Y%m%d)
        if [ $mydate -ne $mytestdate ]; then
            printf 'not valid, results in "%s"\n' "$mytestdate"
            return 1
        else
            printf 'valid\n'
        fi
    fi
else
    printf 'not valid (must be: <yyyymmdd>)\n'
    return 1
fi

就像在busybox(1.29.3和1.23.1)中一样,您会得到如下响应:

lxsys:~# date +%Y%m%d -d 20110229 -D "%Y%m%d"
20110301

我需要以更好的方式验证日期,但我希望主要依赖于系统本身。

因此使用

mytestdate=$(date +%Y%m%d -d $mydate -D %Y%m%d)
if [ $mydate -ne $mytestdate ]; then
    ...
fi

这里有第二个测试 - 我们是否在期望格式(输入,$mydate)和系统解释(输出,$mytestdate)之间有差异... 如果不同,则丢弃该日期。


1
这里有一个函数用于数据验证:
# Script expecting a Date parameter in MM-DD-YYYY format as input
verifyInputDate(){
    echo ${date} | grep '^[0-9][0-9]-[0-9][0-9]-[0-9][0-9][0-9][0-9]$'
    if [ $? -eq 0 ]; then
         echo "Date is valid"
     else
          echo "Date is not valid"
     fi
}

2
这并不比原帖的例子更好。像 99/87/0000 这样的日期也会通过你的 grep。 - JNevill

1
如果X="2016-04-21",则检查下面的值是1还是0。
计算echo $x | cut -c 6-7 echo $x | cut -c 1-4 2>/dev/null | grep -c echo $x | cut -c 9-10 如果该值为1,则有效,否则无效。

你是指“反引号”吗?那么你应该在回答中输入它们,否则它不会起作用,因为现在它们单引号。 - Benjamin W.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接