Как сравнить две даты в оболочке?


88

Как можно сравнить две даты в оболочке?

Вот пример того, как я хотел бы использовать это, хотя это не работает как есть:

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

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

Как мне достичь желаемого результата?


Что вы хотите зациклить? Вы имеете в виду условный, а не цикл?
Мат

Почему вы отметили файлы ? Эти даты на самом деле время файла?
Манатворк

Проверьте это на stackoverflow: stackoverflow.com/questions/8116503/…
slm

Ответы:


107

Правильный ответ все еще отсутствует:

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

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

Обратите внимание, что для этого требуется дата GNU. Эквивалентный dateсинтаксис для BSD date(подобный найденному в OSX по умолчанию)date -j -f "%F" 2014-08-19 +"%s"


10
Не знаю, почему кто-то опроверг это, он довольно точен и не связан с объединением некрасивых строк. Преобразуйте дату в метку времени Unix (в секундах), сравните метки времени Unix.
Николи

Я думаю, для меня главное преимущество этого решения в том, что вы можете легко делать сложения или вычитания. Например, я хотел, чтобы время ожидания составляло 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 он даже пытается установить значение кенеля DST.
Щил

32

Можно использовать стандартное сравнение строк для сравнения хронологического порядка [строк в формате год, месяц, день].

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

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

К счастью, когда [строки, которые используют формат YYYY-MM-DD] сортируются * в алфавитном порядке, они также сортируются * в хронологическом порядке.

(* - отсортировано или сравнено)

Ничего особенного в этом деле не нужно. ура!


Где еще ветка здесь?
Владимир Деспотович

Это единственное решение, которое работало для меня на Sierra MacOS
Владимир Деспотович

Это проще, чем мое решение if [ $(sed -e s/-//g <<< $todate) -ge $(sed -e s/-//g <<< $cond) ];... Этот формат даты был разработан для сортировки с использованием стандартной буквенно-цифровой сортировки или для -сортировки и численной сортировки.
Ctrl-Alt-Delor

Это, безусловно, самый умный ответ здесь
Эд Рэндалл

7

Это проблема не циклических структур, а типов данных.

Эти даты ( todateи cond) являются строками, а не числами, поэтому вы не можете использовать оператор "-ge" для test. (Помните, что обозначение в квадратных скобках эквивалентно команде test.)

Что вы можете сделать, это использовать другую запись для ваших дат, чтобы они были целыми числами. Например:

date +%Y%m%d

выдаст целое число, например 20130715, 15 июля 2013 г. Затем вы сможете сравнить свои даты с "-ge" и эквивалентными операторами.

Обновление: если указаны ваши даты (например, вы читаете их из файла в формате 2013-07-13), то вы можете легко предварительно обработать их с помощью tr.

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

6

Оператор -geработает только с целыми числами, которых нет у ваших дат.

Если ваш скрипт представляет собой скрипт bash или ksh или zsh, вы можете использовать <вместо этого оператор. Этот оператор недоступен в тире или других оболочках, которые не выходят за рамки стандарта POSIX.

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

В любой оболочке вы можете преобразовать строки в числа, соблюдая порядок дат, просто удалив тире.

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

Кроме того, вы можете перейти на традиционные и использовать exprутилиту.

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

Поскольку запуск подпроцессов в цикле может быть медленным, вы можете предпочесть выполнить преобразование, используя конструкции обработки строки оболочки.

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.


Где находится ветка else в этом bash?
Владимир Деспотович

2
Это, кажется, самый правильный ответ. Очень полезно!
Мойтаба Резаян

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

Существует также этот метод из статьи под названием: Простой расчет даты и времени в BASH с unix.com.

Эти функции являются выдержкой из скрипта в этой теме!

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

Если вы устанавливаете gdate на MacOS через brew, это можно легко изменить, перейдя dateв gdate.
Slm

1
Этот ответ был очень полезен в моем случае. Это должно оценить!
Мойтаба Резаян

1

конкретный ответ

Так как я люблю уменьшать количество вилок и я допускаю много хитростей, поэтому у меня есть цель:

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

Ну теперь:

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

Это позволит заново заполнить обе переменные $todateи $cond, используя только одну разветвленную ветвь, с выходом, который date -f -берет stdio для чтения одной даты построчно.

Наконец, вы можете разорвать ваш цикл с

((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
}

Использование встроенной функции , printfкоторая может отображать дату и время с секундами от эпохи (см . man bash;-)

Этот скрипт использует только один форк.

Альтернатива с ограниченными вилками и функцией чтения даты

Это создаст выделенный подпроцесс (только один форк):

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

если вне петли.

Или используйте функцию оболочки 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 -может уменьшить потребность в ресурсах.
Ф. Хаури

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

datetimeцелочисленные преобразования в epochответ @ 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

Причина, по которой это сравнение не работает с дефисной строкой даты, заключается в том, что оболочка предполагает, что число с начальным 0 является восьмеричным, в данном случае это месяц "07". Были предложены различные решения, но самым быстрым и простым является удаление дефисов. Bash имеет функцию подстановки строк, которая делает это быстрым и легким, тогда сравнение может быть выполнено как арифметическое выражение:

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

if (( ${todate//-/} > ${cond//-/} ));
then
    echo larger
fi
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.