У меня есть текстовый файл со значением даты в формате ГГГГММДД в столбце 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.
Используя GNU awk, любые преобразования даты должны выполняться с помощью предоставленных функций даты. Две полезные функции времени для этой задачи — mktime
и strftime
:
mktime(datespec)
: This converts a date specification string,datespec
, of the formYYYY 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 theutc-flag
to indicatedatespec
is in UTC or not.
strftime(format,timestamp)
: This converts an epoch-timetimestamp
into a formatted string (same formatting as thedate
command). You can use theutc-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 (который, вероятно, чище моего, потому что я бы предпочел использовать встроенные функции 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, я указал причину использования close
(вместо того, чтобы пытаться объяснить и ошибаться :p)
Это должно быть $5 = ( (cmd | getline result) > 0 ? result : "N/A" ); close(cmd)
или подобное, чтобы разумно обрабатывать сбои даты/getline. См. awk.freeshell.org/AllAboutGetline
Вам не нужно вызывать для этого внешнюю утилиту 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 }
Ага, это возможно. Если ОП скажет, что это может произойти, я сделаю обновление. Спасибо.
Хороший первый вопрос! Удачи.