Обработка текста: смещение значения даты в столбце на основе другого значения столбца

У меня есть текстовый файл со значением даты в формате ГГГГММДД в ​​столбце 4 и смещением месяца в столбце 5.

a1|b1|c1|20190101|1|1|11|A|D
a1|b1|c1|20190101|2|2|12|B|E
a1|b1|c1|20190101|3|3|13|C|F
a2|b2|c2|20190101|1|4|14|G|J
a2|b2|c2|20190101|2|5|15|H|K
a2|b2|c2|20190101|3|6|16|I|L

Я хотел бы заменить значение в столбце 5 логикой дата -d '(значение в столбце 4) -(значение в столбце 5) месяцы +1 месяц' +'%Y%m%d'

Итак, конечный результат должен выглядеть следующим образом

a1|b1|c1|20190101|20190101|1|11|A|D
a1|b1|c1|20190101|20181201|2|12|B|E
a1|b1|c1|20190101|20181101|3|13|C|F
a2|b2|c2|20190101|20190101|4|14|G|J
a2|b2|c2|20190101|20181201|5|15|H|K
a2|b2|c2|20190101|20181101|6|16|I|L

Я связал с помощью awk -f offsetMonths.awk

BEGIN{
    FS="|"
    OFS = FS
}
{
#   Date field is in column 4, offset is in column 5
#   Replace column 5 with the offset date
    "date -d '"$4" -"$5" months +1 months'  +'%Y%m%d' " | getline l
    $5 = l
    print $0
}

Я получаю

a1|b1|c1|20190101|20190101|1|11|A|D
a1|b1|c1|20190101|20181201|2|12|B|E
a1|b1|c1|20190101|20181101|3|13|C|F
a2|b2|c2|20190101|20181101|4|14|G|J
a2|b2|c2|20190101|20181101|5|15|H|K
a2|b2|c2|20190101|20181101|6|16|I|L

Обратите внимание на неверные значения смещения месяца в строках с 4 по 6.

Хороший первый вопрос! Удачи.

shellter 22.05.2019 17:26
3 метода стилизации элементов HTML
3 метода стилизации элементов HTML
Когда дело доходит до применения какого-либо стиля к нашему HTML, существует три подхода: встроенный, внутренний и внешний. Предпочтительным обычно...
Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
В чем разница между Promise и Observable?
В чем разница между Promise и Observable?
Разберитесь в этом вопросе, и вы значительно повысите уровень своей компетенции.
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Клиент для URL-адресов, cURL, позволяет взаимодействовать с множеством различных серверов по множеству различных протоколов с синтаксисом URL.
3
1
43
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Используя GNU awk, любые преобразования даты должны выполняться с помощью предоставленных функций даты. Две полезные функции времени для этой задачи — mktime и strftime:

  • mktime(datespec): This converts a date specification string, datespec, of the form YYYY MM DD hh mm ss into a Unix epoch time, i.e. the total seconds since 1970 01 01 UTC. Since gawk-4.2.1 you can use the utc-flag to indicate datespec is in UTC or not.

  • strftime(format,timestamp): This converts an epoch-time timestamp into a formatted string (same formatting as the date command). You can use the utc-flag the indicate that the returned time should be in UTC or in the local time-zone.

More info in the GNU awk manual

Теперь код становится: convert.awk

BEGIN {FS=OFS="|"}
{ d=$4
  time=mktime(substr(d,1,4)" "substr(d,5,2)+1-$5" "substr(d,7,2)" 00 00 00")
  $5=strftime("%Y%m%d",time)
  print
}' file

и вы запускаете это с помощью:

$ awk -f convert.awk file.txt

mktime удивительно добавка. Таким образом, строка, которую вы передаете в форме YYYY MM DD hh mm ss, не обязательно должна быть правильной датой, у вас могут быть неправильные значения. Например, строка 2019 01 32 00 00 00 эквивалентна 2019 02 01 00 00 00, а 2019 5 -10 00 00 00 эквивалентна 2019 04 20 00 00 00 и, более того, 2019 -19 -10 00 00 эквивалентна 2017 04 20 00 00.

Примечание:, поскольку мы изменяем время непосредственно в mktime, нам не нужно беспокоиться о переходе на летнее время (см. комментарий).

@EdMorton Ага, действительно. Это кажется опасным только тогда, когда вы меняете время за пределами mktime, вычитая секунды ($ TZ='Europe/Monaco' awk 'BEGIN{t=mktime("2019 05 01 00 00 00"); print strftime("%FT%T",t); print strftime("%FT%T",t-86400*120)})

kvantour 22.05.2019 17:12
Ответ принят как подходящий

В дополнение к ответу kvantour (который, вероятно, чище моего, потому что я бы предпочел использовать встроенные функции awk, а не вызывать команды bash внутри awk), вот ваш исправленный код:

BEGIN{
    FS="|"
    OFS = FS
}
{
#   Date field is in column 4, offset is in column 5
#   Replace column 5 with the offset date

    cmd = "date -d '"$4" -"$5-1" months'  +'%Y%m%d'" # as suggested by @kvantour
    cmd | getline result
    close(cmd)
    $5 = result
    print $0
}

Результат :

a1|b1|c1|20190101|20190101|1|11|A|D
a1|b1|c1|20190101|20181201|2|12|B|E
a1|b1|c1|20190101|20181101|3|13|C|F
a2|b2|c2|20190101|20190101|4|14|G|J
a2|b2|c2|20190101|20181201|5|15|H|K
a2|b2|c2|20190101|20181101|6|16|I|L

Больше информация здесь.

Without the call to close(), awk creates child processes to run the commands, until it eventually runs out of file descriptors for more pipelines.

Я бы прямо упомянул причину close. Кроме того, вы можете использовать $5-1 вместо `- "$5" месяцев + 1 месяц.

kvantour 22.05.2019 15:11

Спасибо @kvantour, я указал причину использования close (вместо того, чтобы пытаться объяснить и ошибаться :p)

Corentin Limier 22.05.2019 15:22

Это должно быть $5 = ( (cmd | getline result) > 0 ? result : "N/A" ); close(cmd) или подобное, чтобы разумно обрабатывать сбои даты/getline. См. awk.freeshell.org/AllAboutGetline

Ed Morton 22.05.2019 15:26

Вам не нужно вызывать для этого внешнюю утилиту date или функции времени, специфичные для gawk, это просто математика:

$ cat tst.awk
BEGIN { FS=OFS="|" }
{
    delta = $5 - 1
    year  = substr($4,1,4)
    month = substr($4,5,2)
    day   = substr($4,7)
    month = month - delta
    if (month <= 0) {
        year--
        month += 12
    }
    $5 = sprintf("%04d%02d%02d",year,month,day)
    print
}
$
$ awk -f tst.awk file
a1|b1|c1|20190101|20190101|1|11|A|D
a1|b1|c1|20190101|20181201|2|12|B|E
a1|b1|c1|20190101|20181101|3|13|C|F
a2|b2|c2|20190101|20190101|4|14|G|J
a2|b2|c2|20190101|20181201|5|15|H|K
a2|b2|c2|20190101|20181101|6|16|I|L

Просто для безопасности может случиться так, что month больше, чем 12, и в то же время может быть на несколько лет позже. if (month > 0 ) { year+= int((month-1)/12); month = (month-1)%12+1}; if (month < 1) { year-=int(month/12)-1; month=month%12 + 12 }

kvantour 22.05.2019 17:40

Ага, это возможно. Если ОП скажет, что это может произойти, я сделаю обновление. Спасибо.

Ed Morton 22.05.2019 17:59

Другие вопросы по теме