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

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"

user93501
источник
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

Вам тоже не хватает циклических структур, как вы планируете получать больше дат?

LPoblet
источник
Это не работает 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] сортируются * в алфавитном порядке, они также сортируются * в хронологическом порядке.

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

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

user2533809
источник
Где еще ветка здесь?
Владимир Деспотович
Это единственное решение, которое работало для меня на 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
sergut
источник
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
InuSasha
источник
Это не работает с тем, 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"
SLM
источник
Это не работает с тем, 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.

s84
источник
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
Джон МакНалти
источник