#awk
#awk
Вопрос:
У меня есть файл с огромными данными в следующем формате я хочу выяснить разницу между двумя временными метками в миллисекундах и добавить последний столбец с разницей во времени в этой строке
22159 | a | 2021-02-26 11:02:03.776 | 2021-02-26 11:02:04.740 <br/>
22160 | b | 2021-02-26 11:35:21.796 | 2021-02-26 11:35:22.674 <br/>
22161 | c | 2021-02-26 11:35:21.806 | 2021-02-26 11:35:22.841 <br/>
22161 | d | 2021-02-26 11:02:18.688 | 2021-02-26 11:02:19.594 <br/>
22182 | e | 2021-02-26 11:06:02.978 | 2021-02-26 11:06:03.815 <br/>
22183 | f | 2021-02-26 11:35:24.911 | 2021-02-26 11:35:25.791 <br/>
22184 | g | 2021-02-26 11:35:25.082 | 2021-02-26 11:35:26.121 <br/>
22199 | h | 2021-02-26 11:09:47.815 | 2021-02-26 11:09:48.499 <br/>
22200 | i | 2021-02-26 11:35:27.562 | 2021-02-26 11:35:28.660 <br/>
22200 | j | 2021-02-26 11:09:49.595 | 2021-02-26 11:09:50.596 <br/>
Вывод, например.
9535 a 2021-02-27 11:02:53.756 2021-02-27 11:02:53.947 0.191
Я привязал нижеприведенную команду:
awk -F'|' 'function convert(t) { cmd = "date %s.%3N -d ""t"" "; cmd|getline timemilli; return timemilli; } { t2=convert($4);t1=convert($3);printf $1"t"$2"t"$3"t"$4"t%.3fn",t2-t1 }' filtered_data
Он отлично работает для небольших файлов, но выдает ошибку для огромных файлов
Ошибка:
awk: cmd. line:1: (FILENAME=filtered_data FNR=516) fatal: cannot open pipe
дата %с.%3N -d «2021-02-27 11:24:05.618» ‘ ( Слишком много открытых файлов)`
Примечание: мой файл составляет около 10 Мб, и я хочу найти разницу в одном кадре. не перебирать каждую строку.
Есть ли другой способ сделать это одним выстрелом?
Комментарии:
1. Сообщение об ошибке, похоже, указывает на несколько открытых файлов, но у вас есть только 1 действительно большой файл, это правильно?
2. да. я открываю только один файл. и, кажется, для некоторых записей он дает неверную разницу: 775875 a 2021-02-27 12:01:44.231 2021-02-27 12:01:44.454 0.000
3. Я думаю, что сообщение об ошибке связано с использованием многих
|
операторов без их закрытия . Смотрите ответ @anubhava.4. У вас, очевидно, есть дата GNU, поскольку вы используете параметры, специфичные для GNU (
-d
и%N
), поэтому у вас также должен быть доступен GNU awk, но GNU awk не будет сбоить, когда у вас слишком много открытых выходных файлов или каналов, это просто замедлит работу, поэтому, хотя он доступен, вы его не вызываете. Так что просто вызов GNU awk вместо любого вызываемого вами awk позволит избежать этой ошибки, но тогда, если у вас есть GNU awk, есть гораздо лучший способ сделать то, что вы пытаетесь сделать, и это с помощью встроенных функций времени вместо создания подоболочки для вызоваdate
дважды на строку ввода.5. Кроме
getline
того, подобное использование незаметно приведет к неправильному выводу, который выглядит допустимым в случаеcmd | getline
сбоя. В тех редких случаях, когда вызовgetline
является правильным подходом, вы должны проверить его результат успеха / неудачи перед использованием переменной, которую он заполняет только в случае успеха, см. awk.freeshell.org/AllAboutGetline за подробностями.
Ответ №1:
Вы можете попробовать это awk
:
awk -F'|' '
function convert(t, cmd, timemilli) {
cmd = "date %s.%3N -d ""t"" "
cmd | getline timemilli
close (cmd) # close this cmd to avoid too many open files
return timemilli
}
{
t2=convert($4)
t1=convert($3)
printf "%st%.3fn", $1"t"$2"t"$3"t"$4, t2-t1
}' filtered_data
Ответ №2:
Используя GNU awk, поскольку mktime()
и gensub()
:
$ gawk '
BEGIN {
FS=" [|] "
}
function s(dt) { # function to deal with dt conversion
return sprintf("%.3fn", mktime(gensub(/[- :.]/," ","g",dt)) gensub(/^[^.] /,"","g",dt))
}
{
$1=$1 # rebuild the record for requested output
print $0,s($3)-s($4) # output
}' file
Вывод:
22159 a 2021-02-26 11:02:03.776 2021-02-26 11:02:04.740 -0.964
22160 b 2021-02-26 11:35:21.796 2021-02-26 11:35:22.674 -0.878
22161 c 2021-02-26 11:35:21.806 2021-02-26 11:35:22.841 -1.035
...
Примечание: mktime()
требуется время для заполнения формы "YYYY MM DD HH MM SS [DST]"
. Выше я злоупотребляю (из-за лени) и заполняю его формой "YYYY MM DD HH MM SS sss"
, где sss
это миллисекунды. Кажется, это работает, но вы можете исправить это с помощью, например substr()
, или аналогичного.
Ответ №3:
Для mawk
1.3.4. Значение перехода на летнее время см. В документации mktime
mawk dst
.
LC_ALL=C awk -v FS=' | ' -v OFS='|' -v RS=' <br/>n' -v OFMT='%.3f' -v dst='-1' '
function fn(s) {
# return epoch seconds, with milliseconds as fraction
gsub(/-|:/," ",s)
return mktime(substr(s,1,19) " " dst) substr(s,21,3)/1000
}{ $(NF 1) = fn($4) - fn($3) }
1
' data
РЕДАКТИРОВАТЬ Отредактировано FS
и RS
соответствует новому формату ввода OP.
Комментарии:
1. что касается
RS=' <br/>n'
— я подозреваю, что<br/>
эти s на самом деле не присутствуют в данных OPs, но были попыткой заставить данные отображаться по одной строке на строку в вопросе путем добавления разрывов HTML до того, как они были правильно отформатированы с помощью markdown.2. @EdMorton:
<br/>
В образце данных исходной публикации не было, поэтому я должен был предположить, что они есть сейчас. Если нетRS
, определение может быть удалено из моего кода.