Как я могу использовать bash (grep / sed / etc) для захвата раздела файла журнала между двумя временными метками?

StackOverflow https://stackoverflow.com/questions/827930

Вопрос

У меня есть набор почтовых журналов:почта.журнал mail.log.0 mail.log.1.gz mail.log.2.gz

каждый из этих файлов содержит хронологически отсортированные строки, которые начинаются с временных меток, таких как:

3 мая 13:21:12 ...

Как я могу легко получить каждую запись в журнале после определенной даты / времени и до другой даты / времени использование bash (и связанные с ними инструменты командной строки) без сравнения каждой отдельной строки?Имейте в виду, что мои даты "до" и "после" могут не точно соответствовать каким-либо записям в файлах журнала.

Мне кажется, что мне нужно определить смещение первой строки, большее, чем начальная временная метка, и смещение последней строки, меньшее, чем конечная временная метка, и каким-то образом вырезать этот раздел.

Это было полезно?

Решение 2

Вот одна из основных идей, как это сделать:

<Ол>
  • Изучите метку даты в файле , чтобы убедиться, что он не имеет отношения
  • Если он может быть подходящим, разархивируйте его при необходимости и проверьте первую и последнюю строки файла, чтобы узнать, содержит ли он время начала или окончания.
  • Если это так, используйте рекурсивную функцию , чтобы определить, содержит ли оно время начала в первой или второй половине файла. Используя рекурсивную функцию, я думаю, что вы можете найти любую дату в лог-файле с миллионами строк и примерно 20 сравнениями.
  • выводит лог-файл (ы) в порядке от смещения первой записи до смещения последней записи (больше никаких сравнений)
  • Чего я не знаю, так это как лучше всего прочитать n-ую строку файла (насколько эффективно использовать tail n + ** n | head 1 **?)

    Любая помощь?

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

    Преобразуйте ваши минимальные / максимальные даты в "секунды с начала эпохи",

    MIN=`date --date="$1" +%s`
    MAX=`date --date="$2" +%s`
    

    Преобразуйте первые слова n в каждой строке журнала в одно и то же,

    L_DATE=`echo $LINE | awk '{print $1 $2 ... $n}'`
    L_DATE=`date --date="$L_DATE" +%s`
    

    Сравнивайте и отбрасывайте строки, пока не достигнете MIN ,

    if (( $MIN > $L_DATE )) ; then continue ; fi
    

    Сравнивайте и печатайте строки, пока не достигнете MAX ,

    if (( $L_DATE <= $MAX )) ; then echo $LINE ; fi
    

    Выход, если вы превысили MAX .

    if (( $L_DATE > $MAX )) ; then exit 0 ; fi
    

    Весь скрипт minmaxlog.sh выглядит следующим образом,

    #!/usr/bin/env bash
    
    MIN=`date --date="$1" +%s`
    MAX=`date --date="$2" +%s`
    
    while true ; do
        read LINE
        if [ "$LINE" = "" ] ; then break ; fi
    
        L_DATE=`echo $LINE | awk '{print $1 " " $2 " " $3 " " $4}'`
        L_DATE=`date --date="$L_DATE" +%s`
    
        if (( $MIN > $L_DATE  )) ; then continue ; fi
        if (( $L_DATE <= $MAX )) ; then echo $LINE ; fi
        if (( $L_DATE >  $MAX )) ; then break ; fi
    done
    

    Я запустил его в этом файле minmaxlog.input ,

    May 5 12:23:45 2009 first line
    May 6 12:23:45 2009 second line
    May 7 12:23:45 2009 third line
    May 9 12:23:45 2009 fourth line
    June 1 12:23:45 2009 fifth line
    June 3 12:23:45 2009 sixth line
    

    вот так,

    ./minmaxlog.sh "May 6" "May 8" < minmaxlog.input
    

    Вы должны просмотреть каждую отдельную строку в нужном вам диапазоне (чтобы определить, находится ли она в нужном вам диапазоне), поэтому я предполагаю, что вы имеете в виду не каждую строку в файле.Как минимум, вам нужно будет просмотреть каждую строку в файле вплоть до первой, находящейся за пределами вашего диапазона (я предполагаю, что строки расположены в порядке даты / времени).

    Это довольно простая схема:

    state = preprint
    for every line in file:
        if line.date >= startdate:
            state = print
        if line.date > enddate:
            exit for loop
        if state == print:
            print line
    

    Вы можете написать это на awk, Perl, Python, даже COBOL, если необходимо, но логика всегда одна и та же.

    Сначала найдите номера строк (скажем, с помощью grep), а затем просто слепо распечатайте этот диапазон строк, это не поможет, поскольку grep также должен просматривать все строки (ВСЕ из них не просто до первого выхода за пределы диапазона, а, скорее всего, дважды, один для первой строки и один для последней).

    Если это то, что вы собираетесь делать довольно часто, вы можете рассмотреть возможность переноса усилия с "каждый раз, когда вы это делаете" на "один раз, когда файл стабилизируется".Примером может быть загрузка строк файла журнала в базу данных, проиндексированную по дате / времени.

    Настройка этого займет некоторое время, но в результате ваши запросы станут намного быстрее.Я не обязательно выступаю за базу данных - вероятно, вы могли бы добиться того же эффекта, разделив файлы журналов на почасовые журналы таким образом:

    2009/
      01/
        01/
          0000.log
          0100.log
          : :
          2300.log
        02/
        : :
    

    Тогда в течение определенного времени вы точно будете знать, с чего начать и прекратить поиски.Диапазон 2009/01/01-15:22 через 2009/01/05-09:07 это привело бы к:

    • некоторый (последний бит) файла 2009/01/01/1500.txt.
    • все файлы 2009/01/01/1[6-9]*.txt.
    • все файлы 2009/01/01/2*.txt.
    • все файлы 2009/01/0[2-4]/*.txt.
    • все файлы 2009/01/05/0[0-8]*.txt.
    • некоторый (первый бит) файла 2009/01/05/0900.txt.

    Конечно, я бы написал скрипт для возврата этих строк, а не пытался делать это каждый раз вручную.

    Может быть, вы можете попробовать это

    sed -n "/BEGIN_DATE/,/END_DATE/p" logfile
    

    Это может быть возможно в среде Bash, но вам действительно следует воспользоваться преимуществами инструментов, которые имеют более встроенную поддержку для работы со строками и датами.Например, Ruby, похоже, имеет встроенную возможность анализировать ваш формат даты.Затем он может преобразовать его в легко сопоставимую временную метку Unix (положительное целое число, представляющее секунды, прошедшие с момента начала эпохи).

    irb> require 'time'
    # => true
    
    irb> Time.parse("May 3 13:21:12").to_i
    # => 1241371272  
    

    Затем вы можете легко написать Ruby-скрипт:

    • Укажите дату начала и окончания.Преобразуйте их в этот номер временной метки Unix.
    • Отсканируйте файлы журнала построчно, преобразуя дату в ее временную метку Unix и проверьте, находится ли она в диапазоне дат начала и окончания.

    Примечание:Сначала хорошо преобразовать в целое число с меткой времени Unix, потому что сравнивать целые числа очень легко и эффективно.

    Вы упомянули "без сравнения каждой отдельной строки". Будет трудно "угадать", где в файле журнала записи становятся слишком старыми или слишком новыми, не проверяя все промежуточные значения.Однако, если действительно наблюдается монотонно возрастающая тенденция, то вы сразу знаете, когда следует прекратить синтаксический анализ строк, потому что, как только следующая запись оказывается слишком новой (или старой, в зависимости от структуры данных), вы знаете, что можете прекратить поиск.Тем не менее, существует проблема поиска первой строки в нужном вам диапазоне.


    Я только что заметил вашу правку.Вот что я бы сказал:

    Если вы в самом деле беспокоясь об эффективном поиске этой начальной и конечной записи, вы могли бы выполнить бинарный поиск для каждой из них.Или, если это кажется излишним или слишком сложным с помощью инструментов bash, у вас может быть эвристика чтения только 5% строк (1 из каждых 20), чтобы быстро получить близкий к точному ответ, а затем при желании уточнить его.Это всего лишь несколько предложений по улучшению производительности.

    Я знаю, что эта ветка старая, но я наткнулся на нее после того, как недавно нашел однострочное решение для моих нужд:

    awk -v ts_start="2018-11-01" -v ts_end="2018-11-15" -F, '$1>=ts_start && $1<ts_end' myfile
    

    В этом случае в моем файле есть записи с разделенными запятыми значениями и отметкой времени в первом поле. Вы можете использовать любой допустимый формат меток времени для начальной и конечной меток времени, и при желании заменить их на переменные оболочки.

    Если вы хотите записать в новый файл, просто используйте обычное перенаправление вывода ( > newfile ), добавленное в конец выше.

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top