Найдите разницу во времени в миллисекундах awk или gawk

#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 , определение может быть удалено из моего кода.