Вопрос или проблема
Как можно сравнить две даты в командной оболочке?
Вот пример того, как я хотел бы это использовать, хотя это не работает как есть:
todate=2013-07-18
cond=2013-07-15
if [ $todate -ge $cond ];
then
break
fi
Как я могу добиться желаемого результата?
Правильный ответ все еще отсутствует:
todate=$(date -d 2013-07-18 +%s)
cond=$(date -d 2014-08-19 +%s)
if [ $todate -ge $cond ];
then
break
fi
Обратите внимание, что это требует GNU date.
- Эквивалентный
date
синтаксис для BSDdate
(как в OSX по умолчанию) этоdate -j -f "%F" 2014-08-19 +"%s"
- Если вы хотите сравнить с текущей датой, используйте
$EPOCHSECONDS
вместо одной из дат.
Можно использовать сравнение строк в стиле ksh для сравнения хронологического порядка [строк в формате год, месяц, день].
date_a=2013-07-18
date_b=2013-07-15
if [[ "$date_a" > "$date_b" ]] ;
then
echo "break"
fi
К счастью, когда [строки, использующие формат YYYY-MM-DD] сортируются* в алфавитном порядке, они также сортируются* в хронологическом порядке.
(* – отсортированы или сравниваются)
В этом случае ничего сложного не нужно. Ура!
Вам не хватает формата даты для сравнения:
#!/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 ]; #поместите цикл там, где вам это нужно
then
echo 'yes';
fi
Вам также не хватает циклических структур, как вы планируете получить больше дат?
Это не проблема циклических структур, а проблема типов данных.
Эти даты (todate
и cond
) являются строками, а не числами, поэтому вы не можете использовать оператор “-ge” команды test
. (Помните, что нотация в квадратных скобках эквивалентна команде test
.)
Что вы можете сделать, так это использовать другую нотацию для ваших дат, чтобы они были целыми числами. Например:
date +%Y%m%d
вернет целое число, например 20130715 для 15 июля 2013 года. Затем вы можете сравнить ваши даты с помощью “-ge” и эквивалентных операторов.
Обновление: если ваши даты заданы (например, вы читаете их из файла в формате 2013-07-13), то вы можете легко предварительно обработать их с помощью tr.
$ echo "2013-07-15" | tr -d "-"
20130715
Оператор -ge
работает только с целыми числами, которыми ваши даты не являются.
Если ваш скрипт на bash или ksh или zsh, вы можете использовать оператор <
вместо этого. Этот оператор недоступен в dash или других оболочках, которые не выходят за пределы стандарта 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
.
Существует также этот метод из статьи под названием: Простое вычисление даты и времени в 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))
}
Использование
# вычислить количество дней между 2 датами
# -s в секундах | -m в минутах | -h в часах | -d в днях (по умолчанию)
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"
Я преобразую строки в временные метки 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
bash специфичный ответ
Так как я люблю уменьшать количество fork’ов и bash позволяет много трюков, вот моя цель:
todate=2013-07-18
cond=2013-07-15
Что ж, теперь:
{ read todate; read cond ;} < <(date -f - +%s <<<"$todate"$'\n'"$cond")
Это повторно заполняет обе переменные $todate
и $cond
, используя только один fork, с выводом команды date -f -
, которая принимает stdin для чтения одной даты за раз.
В конце концов, вы можете выйти из цикла с помощью
((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 старше чем %(%a %d %b %Y)T...\n" $todate $cond
}
Используя встроенный printf
в bash, который может отображать дату и время с секундами с эпохи (см. man bash
😉
Этот скрипт использует только один fork.
Альтернатива с ограниченным количеством fork’ов и функцией чтения даты
Это создаст выделенный подпроцесс (только один 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
}
Замечание Чтобы избежать бесполезных fork’ов, таких как 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: только имеет смысл в цикле `for`, `while` или `until`
если вне цикла.
Или используйте функцию соединителя оболочки:
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
}
В случае, если вы хотите сравнить даты двух файлов, вы можете использовать следующее:
if [ file1 -nt file2 ]
then
echo "file1 новее чем file2"
fi
# или +ot для "старше чем"
Смотрите https://superuser.com/questions/188240/how-to-verify-that-file2-is-newer-than-file1-in-bash
Даты являются строками, а не целыми числами; вы не можете сравнивать их с помощью стандартных арифметических операторов.
один из подходов, которые вы можете использовать, если разделитель гарантированно -
:
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
и старше.
Вы также можете использовать встроенную функцию 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
в соответствии с вашими.
К вашему сведению: на Mac, похоже, если просто ввести дату, такую как “2017-05-05”, в date, то это добавит текущее время к дате, и вы будете получать разные значения каждый раз, когда вы преобразуете его в эпоху. Чтобы получить более последовательное время эпохи для фиксированных дат, включите фиктивные значения для часов, минут и секунд:
date -j -f "%Y-%m-%d %H:%M:%S" "2017-05-05 00:00:00" +"%s"
Это может быть странностью, ограниченной версией date, которая поставлялась с macOS…. проверено на macOS 10.12.6.
Поддерживая ответ @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
преобразования целых чисел
в эпоху
, согласно ответу @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"
, но это выражение неправильно оценивается как Ложь
:
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 является восьмеричным, в данном случае месяц “07”. Существовало много предложенных решений, но самым быстрым и простым является удалить дефисы. Bash имеет функцию замены строк, что делает это быстро и легко, а затем сравнение можно выполнить в арифметическом выражении:
todate=2013-07-18
cond=2013-07-15
if (( ${todate//-/} > ${cond//-/} ));
then
echo larger
fi
Кажется, что все или большинство ответов до сих пор либо предполагают, что вы можете указать формат даты (что вопрос не уточняет явно), либо предполагают, что у вас есть функциональная версия команды date или что-то в этом роде.
Иногда, однако, вы не контролируете формат даты (например, дата истечения срока действия SSL-сертификата, когда дата дается в виде сокращения названия месяца) и у вас нет доступа к более продвинутой команде date. Поэтому хотя это решение не является полностью универсальным, оно работает в стандартной оболочке bash на Linux, Solaris и FreeBSD и обрабатывает названия месяцев (значительно заимствовано из нескольких умных ответов на https://stackoverflow.com/questions/15252383/unix-convert-month-name-to-number):
#!/usr/bin/bash
function monthnumber {
month=$(echo ${1:0:3} | tr '[a-z]' '[A-Z]')
MONTHS="JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC"
tmp=${MONTHS%%$month*}
month=${#tmp}
monthnumber=$((month/3+1))
printf "%02d\n" $monthnumber
}
# Или, жертвуя некоторой гибкостью и краткостью, вы получите больше читаемости:
function monthnumber2 {
case $(echo ${1:0:3} | tr '[a-z]' '[A-Z]') in
JAN) monthnumber="01" ;;
FEB) monthnumber="02" ;;
MAR) monthnumber="03" ;;
APR) monthnumber="04" ;;
MAY) monthnumber="05" ;;
JUN) monthnumber="06" ;;
JUL) monthnumber="07" ;;
AUG) monthnumber="08" ;;
SEP) monthnumber="09" ;;
OCT) monthnumber="10" ;;
NOV) monthnumber="11" ;;
DEC) monthnumber="12" ;;
esac
printf "%02d\n" $monthnumber
}
TODAY=$( date "+%Y%m%d" )
echo "GET /" | openssl s_client -connect github.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > tmp.pem
cert_expiry_date=$(openssl x509 -in tmp.pem -noout -enddate | cut -d'=' -f2)
month_name=$(echo $cert_expiry_date | cut -d' ' -f1)
month_number=$( monthnumber $month_name )
cert_expiration_datestamp=$( echo $cert_expiry_date | awk "{printf \"%d%02d%02d\",\$4,\"${month_number}\",\$2}" )
echo "сравнить: [ $cert_expiration_datestamp -gt $TODAY ]"
if [ $cert_expiration_datestamp -gt $TODAY ] ; then
echo "все в порядке, дата истечения сертификата в будущем."
else
echo "ПРЕДУПРЕЖДЕНИЕ: дата истечения срока сертификата в прошлом."
fi
Ответ или решение
Сравнение двух дат в оболочке Linux
Сравнение дат в оболочке Linux может показаться простой задачей, однако, из-за особенностей работы с форматами данных и различиями в командах, важно понимать, как правильно это сделать. В данной статье мы рассмотрим различные методы для сравнения дат в оболочке, а также некоторые нюансы.
Способы сравнения дат
-
Использование
date
для преобразования в метки времениСамый надёжный способ сравнения двух дат — преобразование их в метки времени (timestamp) с помощью команды
date
, которая позволяет легко сравнивать числа. Для этого вы можете воспользоваться следующими командами:todate=$(date -d "2013-07-18" +%s) cond=$(date -d "2013-07-15" +%s) if [ "$todate" -ge "$cond" ]; then echo "Дата больше или равна" fi
Важно отметить, что данный метод требует использования GNU версии
date
. На системах BSD (например, в macOS) синтаксис будет несколько изменён:todate=$(date -j -f "%Y-%m-%d" "2013-07-18" +%s) cond=$(date -j -f "%Y-%m-%d" "2013-07-15" +%s)
-
Сравнение в формате YYYY-MM-DD
Даты, записанные в формате
YYYY-MM-DD
, можно сравнивать как строки при помощи сочетания[[ ... ]]
:date_a="2013-07-18" date_b="2013-07-15" if [[ "$date_a" > "$date_b" ]]; then echo "date_a больше date_b" fi
В этом случае вы получаете простой и быстрый способ сравнения, так как строки в этом формате уже отсортированы по времени.
-
Числовое преобразование для сравнения
Если вы предпочитаете использовать числовые сравнения, вы можете удалить дефисы из дат:
todate="2013-07-18" cond="2013-07-15" if (( ${todate//-/} >= ${cond//-/} )); then echo "todate больше или равна cond" fi
Этот метод требует, чтобы данные представляли собой корректные даты в формате
YYYY-MM-DD
. -
Сравнение дат без превращения в метки времени
Если доступ к командам ограничен, можно выполнить разбор дат по компонентам и сравнить их:
IFS=- read year_a month_a day_a <<< "$date_a" IFS=- read year_b month_b day_b <<< "$date_b" if (( year_a > year_b )) || { (( year_a == year_b )) && (( month_a > month_b )); } || { (( year_a == year_b && month_a == month_b )) && (( day_a > day_b )); }; then echo "date_a более поздняя" fi
Заключение
Сравнение дат в Linux требует понимания форматов данных и методов, доступных в вашей среде. Использование команды date
для преобразования в метки времени, а также простые строковые сравнения — это два наиболее распространённых подхода. Выбор метода зависит от вашей конкретной ситуации, доступности команд и требований к точности.
Используйте указанные методы для вашего скрипта в оболочке, чтобы достигнуть точного сравнения дат, а также повысить читаемость и поддержку ваших скриптов.