- Вопрос или проблема
- Сценарий
- Пример container1-last-deploy.txt:
- MacOS date – конвертировать строку даты в секунды с начала Эпохи
- GNU date – конвертировать строку даты в секунды с начала Эпохи
- Вычитание из вашей текущей даты
- Ответ или решение
- Шаг 1: Подготовка данных
- Шаг 2: Преобразование даты в секунды
- Шаг 3: Получение текущей даты в секундах
- Шаг 4: Вычисление разницы
- Шаг 5: Проверка условия
- Полный скрипт
- Заключение
Вопрос или проблема
Сценарий
У меня есть контейнеры, даты последнего развертывания в которых имеют следующий формат:
месяц/дата/год или февр/11/2024.
Я не могу изменить способ, которым они выводят дату последнего развертывания.
Я хотел бы вычесть дату последнего развертывания из текущей даты, а затем записать в файл, если дата последнего развертывания больше 2 месяцев.
Пример container1-last-deploy.txt:
последнее развертывание: янв/01/2024
У меня есть несколько таких файлов.
Проблема в том, что месяц указан буквами. Я мог бы использовать регулярные выражения, чтобы получить дату.
Одно из решений, которое я придумал, – иметь месяцы в словаре, например:
dict=(
['янв']=1
['фев']=2
)
Но, вероятно, есть и лучший способ.
Можно ли преобразовать названия месяцев в цифры?
Это для bash на macos, предпочтительно решение для bash 3.
MacOS date
– конвертировать строку даты в секунды с начала Эпохи
Команда date
может распознавать месяцы в формате строк благодаря функции strptime(3)
библиотеки libc. У меня нет macos, поэтому я не могу протестировать свой ответ, но согласно странице man для date
в macos:
date [-jRu] -f input_fmt new_date [+output_fmt]
[…]
-f Используйте input_fmt в качестве форматной строки для разбора new_date, вместо использования
формата по умолчанию [[[mm]dd]HH]MM[[cc]yy][.ss]. Разбор производится с помощью strptime(3).
Теперь вам просто нужно понять, как форматировать вашу входную дату на странице man strptime(3)
:
%b или %B или %h
Название месяца в соответствии с текущей локалью, в сокращенной
форме или полное название.
%d или %e
День месяца (1-31).
%Y Год, включая век (например, 1991).
Таким образом, в соответствии с этим, входной формат должен быть: "%b/%e/%Y"
.
Далее, вам нужно использовать следующий флаг для date
:
-j Не пытайтесь установить дату. Это позволяет использовать флаг -f в дополнение к
опции +, чтобы преобразовать один формат даты в другой. Обратите внимание, что любые даты
или временные компоненты, не указанные в строке формата -f, получают свои значения от текущего времени.
И, наконец, поскольку вы хотите иметь возможность вычесть текущее время из вашего результата, вам нужно убедиться, что ваш +output_fmt будет:
%s Количество секунд с начала Эпохи, 1970-01-01 00:00:00
+0000 (UTC). У високосных секунд нет учета, если поддержка високосных
секунд недоступна.
Таким образом, команда результата, которая преобразует ваш формат даты в эпоху, должна быть:
$ DATE="янв/01/2024"
$ date -jf "%b/%e/%Y" $DATE +%s
1704060000
Снова, поскольку у меня нет macos, я не могу протестировать это для подтверждения. Я также не уверен, примет ли это месяц в нижнем регистре, но если вам нужно сделать первую букву месяца заглавной, вы можете просто заменить $DATE
на ${DATE^}
.
Вместо этого для полноты я также покажу, как выполнить ту же операцию с:
GNU date
– конвертировать строку даты в секунды с начала Эпохи
Это немного легче, так как GNU-версия date
дает больше свободы.
Согласно его странице man:
--date=STRING - это в основном свободный формат читаемой человеком даты
строки, такой как "Вс, 29 Фев 2004 16:21:42 -0800" или "2004-02-29
16:21:42" или даже "в следующий четверг". Строка даты может содержать
элементы, указывающие на календарную дату, время суток, часовой пояс, день
недели, относительное время, относительную дату и числа. Пустая строка
указывает на начало дня. Формат строки даты
более сложен, чем это легко задокументировать здесь, но полностью
описан в документации info.
Итак, согласно моему тесту на моем компьютере, вам просто нужно убрать слэши из вашего ввода, чтобы команда date
смогла его понять:
$ DATE="янв/01/2024"
$ echo ${DATE//\// }
янв 01 2024
$ date --date "${DATE//\// }" +%s
1704060000
Вычитание из вашей текущей даты
Теперь у вас есть время Эпохи вашей первоначальной даты. Просто сохраните его в переменной. Например, в MacOS:
$ ORIG_DATE=$(date -jf "%b/%e/%Y" $DATE +%s)
Чтобы увидеть текущую дату в секундах с начала эпохи:
$ date +%s
1704965965
Теперь, чтобы вычислить разницу в секундах, это тривиально:
$ echo $(( $(date +%s ) - $ORIG_DATE ))
905967
Вам следует попытаться получить точные требования (возможно, у вас они есть, но вы не указали их здесь).
-
Одно из них – это локаль, которая предполагается для сокращений месяцев. Например, в немецкой локали несколько названий месяцев могут вызвать проблемы при разборе дат, напечатанных английским приложением (например,
май
– этоmai
на немецком, иокт
– этоokt
).Если имя месяца, которое используется, соответствует текущей локали, простая команда
date -d "$(echo янв/01/2024 | tr / ' ')"
распарсит дату развертывания. -
Другой вопрос – как вы хотите вычислить разницу дат:
-
Если вы ориентируетесь на количество дней (60 дней как приближение), вы можете преобразовать дату развертывания и текущую дату в секунды, применяя форматный аргумент
+%s
, вычитая эти числа и проверяя, больше ли результат 60*86400. -
Если вы хотите придерживаться какого-то более сложного правила (например, сравнение дня месяца), вы также можете сделать это в bash, но это будет сложнее.
-
Это, с использованием любого date
и любого awk
, выполнит то, что вы описали:
$ awk -v now="$(date +%m/%Y)" -v dif=2 -F'[ /]' '
BEGIN {
split(now,n)
now = n[1] + (n[2] * 12)
}
{
mth = ( index("янвфевмарапрмайюнjulавгсепоктно...",$3) + 2 ) / 3
cur = mth + ($5 * 12)
}
(now - cur) > dif
' file
последнее развертывание: окт/01/2023
Вышеуказанное было выполнено на этом входном файле:
$ cat file
последнее развертывание: окт/01/2023
последнее развертывание: ноя/01/2023
последнее развертывание: дек/01/2023
последнее развертывание: янв/01/2024
Если названия месяцев в вашем вводе не все в нижнем регистре, например, Янв
или ЯНВ
, просто измените $3
на tolower($3)
в скрипте.
Используя Raku (ранее известный как Perl_6)
ОП спрашивает: “Можно ли преобразовать месяцы, указанные буквами, в числа?” В Raku вы можете начать так:
~$ raku -e 'my %months = [Z=>] <Янв Фев Мар Апр Май Июн Июл Авг Сен Окт Ноя Дек>, 1..12; %months.sort(*.value).say;'
(Янв => 1 Фев => 2 Мар => 3 Апр => 4 Май => 5 Июн => 6 Июл => 7 Авг => 8 Сен => 9 Окт => 10 Ноя => 11 Дек => 12)
Теперь вы можете преобразовать дату в валидную ISO 8601 дату:
~$ raku -pe 'BEGIN my %months = [Z=>] <Янв Фев Мар Апр Май Июн Июл Авг Сен Окт Ноя Дек>, 1..12; \
s{ ^ .+? <( ( <.alpha>**3 ) \/ (\d**1..2) \/ (\d**4) } = "$2-{sprintf q[%02d], %months{$0.tclc}}-$1".Date;' file
последнее развертывание: 2024-01-01
Не переживайте, мы всегда можем переформатировать с помощью mm-dd-yyyy
рутины Raku:
~$ raku -pe 'BEGIN my %months = [Z=>] <Янв Фев Мар Апр Май Июн Июл Авг Сен Окт Ноя Дек>, 1..12; \
s{ ^ .+? <( ( <.alpha>**3 ) \/ (\d**1..2) \/ (\d**4) } = "$2-{sprintf q[%02d], %months{$0.tclc}}-$1".Date.mm-dd-yyyy("/");' file
последнее развертывание: 01/01/2024
Поскольку этот элемент Raku теперь является объектом Date, вы можете производить вычисления с ним:
~$ raku -pe 'BEGIN my %months = [Z=>] <Янв Фев Мар Апр Май Июн Июл Авг Сен Окт Ноя Дек>, 1..12; \
s{ ^ .+? <( ( <.alpha>**3 ) \/ (\d**1..2) \/ (\d**4) } = Date.new(now) - ""$2-{sprintf q[%02d], %months{$0.tclc}}-$1".Date ~ " дней назад";' file
последнее развертывание: 46 дней назад
Смотрите ссылки ниже для дальнейших примеров кода.
https://docs.raku.org/language/temporal.html
https://github.com/Raku/examples/tree/master/categories/cookbook/03dates-and-times
https://raku.org
macos поставляется с устаревшей версией bash, но с текущими версиями zsh, которая является намного более удобной оболочкой. zsh имеет strftime
и strftime -r
как встроенные интерфейсы для strftime()
и strptime()
, чтобы ф
орматировать и p
арсить временные метки, так что вы можете делать
#! /bin/zsh -
zmodload zsh/datetime || exit
date=янв/01/2024
now=$EPOCHSECONDS
LC_ALL=C strftime -rs then %b/%d/%Y $date || exit
(( month = 30 * (day = 24 * (hour = 60 * (minute = 60 * (second = 1)))) ))
printf '%s в полночь утром было %d секунд (%d дней) назад.\n' \
$date \
'сейчас - тогда' \
'(сейчас - тогда) / день'
if (( now - then > 2 * month )) print Это было больше 2 месяцев назад.
Обратите внимание на LC_ALL=C
, чтобы работать с названиями месяцев на английском независимо от локали пользователя. Вам также потребуется это с большинством реализаций date
, которые поддерживают разбор временных меток.
Обратите внимание на приближения:
- один день – это 24 часа по 3600 секунд, в то время как на практике в часовых поясах, которые реализуют переход на летнее/зимнее время, есть дни продолжительностью 23 часа и дни продолжительностью 25 часов.
- один месяц – это 30 дней, в то время как некоторые месяцы имеют 28, 29 или 31 день.
Ответ или решение
Чтобы вычесть дату, представленную в формате ‘месяц/день/год’ (например, ‘янв/01/2024’), из текущей даты в MacOS с использованием bash, следуйте пошаговым инструкциям, описанным ниже. Эта задача включает в себя преобразование названия месяца из буквенного формата в числовой, использование утилиты date
для работы с датами и проверку временного интервала.
Шаг 1: Подготовка данных
Предположим, у нас есть файл с последней датой развертывания контейнера в формате, например:
last deployed: jan/01/2024
Для начала важно прочитать данные из файла и извлечь дату. Вы можете использовать команду cat
для чтения содержимого файла:
DATE=$(cat container1-last-deploy.txt | grep 'last deployed:' | awk '{ print $3 }')
Шаг 2: Преобразование даты в секунды
Теперь, имея дату в строковом формате, давайте преобразуем ее в количество секунд с начала эпохи (Epoch time) с помощью команды date
. Сначала нужно указать формат, в котором будет произведено чтение даты:
ORIG_DATE=$(date -jf "%b/%d/%Y" "$DATE" +%s)
Флаги:
-j
— не пытаться установить дату системы.-f
— использовать указанный формат для анализа даты.+%s
— выводит дату в виде секунд с начала эпохи.
Шаг 3: Получение текущей даты в секундах
Получим текущее время в секундах:
CURRENT_DATE=$(date +%s)
Шаг 4: Вычисление разницы
Теперь, когда мы имеем обе даты в формате секунды, мы можем вычислить разницу:
DIFF=$(($CURRENT_DATE - $ORIG_DATE))
Шаг 5: Проверка условия
Чтобы определить, превышает ли разница две месяца, необходимо преобразовать месяцы в секунды. В среднем месяц составляет 30,44 дня (учитывая високосные годы). Значит, два месяца — это примерно:
TWO_MONTHS=$((2 * 30 * 24 * 60 * 60)) # 2 месяца в секундах
Теперь выполните проверку:
if [ "$DIFF" -gt "$TWO_MONTHS" ]; then
echo "Last deployed date is greater than 2 months ago."
echo "$DATE" >> output.txt # Запишите результат в файл
fi
Полный скрипт
Соберем весь процесс в один скрипт:
#!/bin/bash
# Чтение даты из файла
DATE=$(cat container1-last-deploy.txt | grep 'last deployed:' | awk '{ print $3 }')
# Преобразование даты в эпоху
ORIG_DATE=$(date -jf "%b/%d/%Y" "$DATE" +%s)
CURRENT_DATE=$(date +%s)
# Вычисление разницы в секундах
DIFF=$(($CURRENT_DATE - $ORIG_DATE))
TWO_MONTHS=$((2 * 30 * 24 * 60 * 60)) # 2 месяца в секундах
# Проверка условия
if [ "$DIFF" -gt "$TWO_MONTHS" ]; then
echo "Last deployed date is greater than 2 months ago."
echo "$DATE" >> output.txt # Записать в файл
fi
Заключение
Следуя приведенным выше шагам, вы сможете легко вычесть дату, записанную в текстовом формате, из текущей даты, проверив, превышает ли она два месяца, и, при необходимости, записать результат в файл. Этот процесс использует стандартные инструменты Linux, доступные в MacOS, и даст надежный результат.