Как я могу использовать bash (grep / sed / etc), чтобы получить часть файла журнала между 2 временными метками? - PullRequest
3 голосов
/ 06 мая 2009

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

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

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

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

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

Ответы [ 6 ]

5 голосов
/ 06 мая 2009

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

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
1 голос
/ 20 сентября 2011

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

sed -n "/BEGIN_DATE/,/END_DATE/p" logfile
1 голос
/ 06 мая 2009

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

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

Чего я не знаю, так это: как лучше всего прочитать n-ую строку файла (насколько эффективно использовать tail n + ** n | head 1 **?)

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

1 голос
/ 06 мая 2009

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

Это довольно простой шаблон:

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.

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

0 голосов
/ 15 ноября 2018

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

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

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

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

0 голосов
/ 06 мая 2009

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

irb> require 'time'
# => true

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

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

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

Примечание. Преобразование в целое число Unix Timestamp в первую очередь удобно, поскольку сравнение целых чисел очень просто и эффективно.

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


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

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

...