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

Я полагаю что, как средство защиты, что-либо с user в fstab автоматически смонтирован noexec если exec явно задано в fstab.

90
30.01.2017, 17:14
13 ответов

Вы пропускаете формат даты для сравнения:

#!/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

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

32
27.01.2020, 19:30
  • 1
    Это не работает с date поставленный с macOS. –  Flimm 08.12.2016, 11:49

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

Те даты (todate и cond) строки, не числа, таким образом, Вы не можете использовать "-GE" оператор test. (Помните, что нотация квадратной скобки эквивалентна команде test.)

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

date +%Y%m%d

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

Обновление: если Ваши даты даны (например, Вы читаете их из файла в 13.07.2013 форматах), затем, можно предварительно обработать их легко с TR.

$ echo "2013-07-15" | tr -d "-"
20130715
7
27.01.2020, 19:30

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

Если Ваш сценарий является ударом или 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.

6
27.01.2020, 19:30
  • 1
    Где еще ответвление в этом ударе? –  Vladimir Despotovic 15.12.2016, 16:12

От названной статьи существует также этот метод: Простая дата и время calulation в 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"
1
27.01.2020, 19:30
  • 1
    Это не работает с date это поставлется с macOS. –  Flimm 08.12.2016, 11:51

даты являются строками, не целыми числами; Вы не можете сравнить их со стандартными арифметическими операторами.

один подход, который можно использовать, если разделитель, как гарантируют, будет -:

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
27.01.2020, 19:30

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

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

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

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

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

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

33
27.01.2020, 19:30

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

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

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

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

114
27.01.2020, 19:30

Вы также можете использовать встроенную функцию 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
27.01.2020, 19:30

Я преобразовываю строки в уникс-время (секунды с 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
1
27.01.2020, 19:30

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

Поскольку я люблю уменьшать форки и допускает много трюков, есть моя цель:

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
}

Nota Чтобы предотвратить бесполезные форки типа todate=$(myDate 2013-07-18), переменная должна задаваться самой функцией. А чтобы разрешить свободный синтаксис (с кавычками или без кавычек для datestring), имя переменной должно быть последним аргументом.

Тогда сравнение дат:

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

(Wich не совсем одинаковые: .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
}
1
27.01.2020, 19:30

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
27.01.2020, 19:30

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

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

if (( ${todate//-/} > ${cond//-/} ));
then
    echo larger
fi
0
27.01.2020, 19:30

Вторя ответу @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
27.01.2020, 19:30

Теги

Похожие вопросы