如何比较外壳中的两个日期?


88

如何在shell中比较两个日期?

这是一个我想如何使用它的示例,尽管它不能按原样工作:

todate=2013-07-18
cond=2013-07-15

if [ $todate -ge $cond ];
then
    break
fi                           

如何获得理想的结果?


您想循环播放什么?您的意思是有条件而不是循环吗?
马太福音

为什么标记文件?这些日期实际上是文件时间吗?
manatwork

看看这个计算器上:stackoverflow.com/questions/8116503/...
SLM

Answers:


106

正确的答案仍然缺失:

todate=$(date -d 2013-07-18 +%s)
cond=$(date -d 2014-08-19 +%s)

if [ $todate -ge $cond ];
then
    break
fi  

请注意,这需要GNU日期。dateBSD 的等效语法date(如默认情况下在OSX中找到的)是date -j -f "%F" 2014-08-19 +"%s"


10
Donno为什么有人不赞成这一点,它非常准确,并且不涉及串联丑陋的字符串。将您的日期转换为Unix时间戳(以秒为单位),比较Unix时间戳。
Nicholi

我认为,此解决方案的主要优点是,您可以轻松进行加法或减法。例如,我希望有一个x秒的超时时间,但是我的第一个想法(timeout=$(`date +%Y%m%d%H%M%S` + 500))显然是错误的。
iGEL

您可以使用date -d 2013-07-18 +%s%N
typelogic

32

您缺少比较的日期格式:

#!/bin/bash

todate=$(date -d 2013-07-18 +"%Y%m%d")  # = 20130718
cond=$(date -d 2013-07-15 +"%Y%m%d")    # = 20130715

if [ $todate -ge $cond ]; #put the loop where you need it
then
 echo 'yes';
fi

您也缺少循环结构,如何计划获得更多日期?


这不适date用于macOS随附的产品。
Flimm

date -d是非标准的,在典型的UNIX上不起作用。在BSD上,它甚至尝试设置kenel值DST
schily

32

可以使用标准字符串比较来比较[年,月,日格式的字符串的时间顺序]。

date_a=2013-07-18
date_b=2013-07-15

if [[ "$date_a" > "$date_b" ]] ;
then
    echo "break"
fi

值得庆幸的是,当[使用YYYY-MM-DD格式的字符串]按字母顺序排序*时,它们也按时间顺序排序*。

(*-排序或比较)

在这种情况下,不需要花哨的东西。好极了!


这里的else分支在哪里?
弗拉基米尔·德斯波托维奇

这是唯一适用于Sierra MacOS的解决方案
Vladimir Despotovic

这比我对if [ $(sed -e s/-//g <<< $todate) -ge $(sed -e s/-//g <<< $cond) ];...的解决方案更简单。此日期格式设计为使用标准的字母数字排序进行排序,或者用于-剥离和数字排序。
ctrl-alt-delor

这无疑是最明智的答案
Ed Randall

7

这不是循环结构的问题,而是数据类型的问题。

这些日期(todatecond)是字符串,而不是数字,因此您不能使用的“ -ge”运算符test。(请记住,方括号符号等效于命令test。)

您可以做的是为日期使用不同的符号,以便它们是整数。例如:

date +%Y%m%d

会在2013年7月15日产生一个类似20130715的整数。然后,您可以将日期与“ -ge”和等效运算符进行比较。

更新:如果给出了日期(例如,您正在从2013-07-13格式的文件中读取日期),则可以使用tr轻松对其进行预处理。

$ echo "2013-07-15" | tr -d "-"
20130715

6

运算符-ge仅适用于整数,而日期则无效。

如果您的脚本是bash或ksh或zsh脚本,则可以改用<运算符。在破折号或其他不超过POSIX标准的外壳程序中,此运算符不可用。

if [[ $cond < $todate ]]; then break; fi

在任何shell中,都可以通过删除破折号将字符串转换为数字,同时遵守日期顺序。

if [ "$(echo "$todate" | tr -d -)" -ge "$(echo "$cond" | tr -d -)" ]; then break; fi

另外,您也可以使用传统的expr实用程序。

if expr "$todate" ">=" "$cond" > /dev/null; then break; fi

由于在循环中调用子流程可能很慢,因此您可能更喜欢使用shell字符串处理构造进行转换。

todate_num=${todate%%-*}${todate#*-}; todate_num=${todate_num%%-*}${todate_num#*-}
cond_num=${cond%%-*}${cond#*-}; cond_num=${cond_num%%-*}${cond_num#*-}
if [ "$todate_num" -ge "$cond_num" ]; then break; fi

当然,如果您可以首先检索不带连字符的日期,则可以将其与进行比较-ge


这个bash中的else分支在哪里?
弗拉基米尔·德斯波托维奇

2
这似乎是最正确的答案。很有帮助!
Mojtaba Rezaeian '18 -10-21

1

我将字符串转换为unix时间戳(自1.1.1970 0:0:0以来的秒数)。这些可以轻松比较

unix_todate=$(date -d "${todate}" "+%s")
unix_cond=$(date -d "${cond}" "+%s")
if [ ${unix_todate} -ge ${unix_cond} ]; then
   echo "over condition"
fi

这不适date用于macOS随附的。
Flimm

似乎与unix.stackexchange.com/a/170982/100397相同,后者在您发布之前发布了
roaima

1

标题为文章的文章中也提供了这种方法:unix.com的BASH中的简单日期和时间计算

这些功能摘自该线程中脚本!

date2stamp () {
    date --utc --date "$1" +%s
}

dateDiff (){
    case $1 in
        -s)   sec=1;      shift;;
        -m)   sec=60;     shift;;
        -h)   sec=3600;   shift;;
        -d)   sec=86400;  shift;;
        *)    sec=86400;;
    esac
    dte1=$(date2stamp $1)
    dte2=$(date2stamp $2)
    diffSec=$((dte2-dte1))
    if ((diffSec < 0)); then abs=-1; else abs=1; fi
    echo $((diffSec/sec*abs))
}

用法

# calculate the number of days between 2 dates
    # -s in sec. | -m in min. | -h in hours  | -d in days (default)
    dateDiff -s "2006-10-01" "2006-10-31"
    dateDiff -m "2006-10-01" "2006-10-31"
    dateDiff -h "2006-10-01" "2006-10-31"
    dateDiff -d "2006-10-01" "2006-10-31"
    dateDiff  "2006-10-01" "2006-10-31"

这不适date用于macOS随附的。
Flimm

如果您通过brew在MacOS上安装gdate,则可以通过将其更改date为来轻松进行修改以使其工作gdate
slm

1
这个答案对我的案子很有帮助。它应该评分!
Mojtaba Rezaeian

1

特定答案

由于我喜欢减少叉子和确实有很多技巧,所以我的目的是:

todate=2013-07-18
cond=2013-07-15

现在好了:

{ read todate; read cond ;} < <(date -f - +%s <<<"$todate"$'\n'"$cond")

这将重新填充两个变量$todate$cond,只用一把叉子,用ouptut date -f -至极取标准输入输出读取一行一个日期。

最后,您可以打破循环

((todate>=cond))&&break

或作为功能

myfunc() {
    local todate cond
    { read todate
      read cond
    } < <(
      date -f - +%s <<<"$1"$'\n'"$2"
    )
    ((todate>=cond))&&return
    printf "%(%a %d %b %Y)T older than %(%a %d %b %Y)T...\n" $todate $cond
}

使用的内置printfwich可以将日期时间渲染为距纪元秒(请参见man bash;-)

此脚本仅使用一个fork。

有限的货叉和日期读取器功能的替代品

这将创建一个专用的子进程(仅一个分支):

mkfifo /tmp/fifo
exec 99> >(exec stdbuf -i 0 -o 0 date -f - +%s >/tmp/fifo 2>&1)
exec 98</tmp/fifo
rm /tmp/fifo

输入和输出打开时,可以删除fifo条目。

功能:

myDate() {
    local var="${@:$#}"
    shift
    echo >&99 "${@:1:$#-1}"
    read -t .01 -u 98 $var
}

注意为了防止类似的无用派生todate=$(myDate 2013-07-18),变量应由函数自己设置。为了允许自由语法(日期字符串带或不带引号),变量名称必须是最后一个参数。

然后进行日期比较:

myDate 2013-07-18        todate
myDate Mon Jul 15 2013   cond
(( todate >= cond )) && {
    printf "To: %(%c)T > Cond: %(%c)T\n" $todate $cond
    break
}

可能会呈现:

To: Thu Jul 18 00:00:00 2013 > Cond: Mon Jul 15 00:00:00 2013
bash: break: only meaningful in a `for', `while', or `until' loop

如果在循环之外。

或使用shell-connector bash函数:

wget https://github.com/F-Hauri/Connector-bash/raw/master/shell_connector.bash

要么

wget https://f-hauri.ch/vrac/shell_connector.sh

(两者并不完全相同:.sh如果没有来源,则包含完整的测试脚本)

source shell_connector.sh
newConnector /bin/date '-f - +%s' @0 0

myDate 2013-07-18        todate
myDate "Mon Jul 15 2013"   cond
(( todate >= cond )) && {
    printf "To: %(%c)T > Cond: %(%c)T\n" $todate $cond
    break
}

分析bash很好地演示了如何使用date -f -减少资源需求。
F. Hauri

0

日期是字符串,不是整数;您无法将它们与标准算术运算符进行比较。

如果可以保证分隔符为-:,则可以使用一种方法:

IFS=-
read -ra todate <<<"$todate"
read -ra cond <<<"$cond"
for ((idx=0;idx<=numfields;idx++)); do
  (( todate[idx] > cond[idx] )) && break
done
unset IFS

这可以追溯到bash 2.05b.0(1)-release


0

您还可以使用mysql的内置函数比较日期。结果以天为单位。

from_date="2015-01-02"
date_today="2015-03-10"
diff=$(mysql -u${DBUSER} -p${DBPASSWORD} -N -e"SELECT DATEDIFF('${date_today}','${from_date}');")

只需根据您的插入值$DBUSER$DBPASSWORD变量即可。


0

FWIW:在Mac上,似乎只将“ 2017-05-05”之类的日期输入到日期中,会将当前时间追加到该日期,每次将其转换为纪元时间,您将获得不同的值。为了使固定日期的纪元时间更加一致,请包括小时,分钟和秒的虚拟值:

date -j -f "%Y-%m-%d %H:%M:%S" "2017-05-05 00:00:00" +"%s"

这可能是奇怪的,仅限于macOS附带的日期版本。...在macOS 10.12.6上测试。


0

回显@Gilles答案(“ -ge运算符仅适用于整数”),这是一个示例。

curr_date=$(date +'%Y-%m-%d')
echo "$curr_date"
2019-06-20

old_date="2019-06-19"
echo "$old_date"
2019-06-19

datetimeepoch@ mike-q在https://stackoverflow.com/questions/10990949/convert-date-time-string-to-epoch-in-bash中的答案,将整数转换为:

curr_date_int=$(date -d "${curr_date}" +"%s")
echo "$curr_date_int"
1561014000

old_date_int=$(date -d "${old_date}" +"%s")
echo "$old_date_int"
1560927600

"$curr_date"大于(大于)"$old_date",但是此表达式错误地计算为False

if [[ "$curr_date" -ge "$old_date" ]]; then echo 'foo'; fi

...在此处明确显示:

if [[ "$curr_date" -ge "$old_date" ]]; then echo 'foo'; else echo 'bar'; fi
bar

整数比较:

if [[ "$curr_date_int" -ge "$old_date_int" ]]; then echo 'foo'; fi
foo

if [[ "$curr_date_int" -gt "$old_date_int" ]]; then echo 'foo'; fi
foo

if [[ "$curr_date_int" -lt "$old_date_int" ]]; then echo 'foo'; fi

if [[ "$curr_date_int" -lt "$old_date_int" ]]; then echo 'foo'; else echo 'bar'; fi
bar

0

此比较不适用于带连字符的日期字符串的原因是shell假定前导0的数字是八进制,在这种情况下为月份“ 07”。已经提出了各种解决方案,但是最快和最简单的方法是删除连字符。Bash具有字符串替换功能,该功能使此操作变得轻松快捷,然后可以将其作为算术表达式进行比较:

todate=2013-07-18
cond=2013-07-15

if (( ${todate//-/} > ${cond//-/} ));
then
    echo larger
fi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.