Разделить файл access.log по датам с помощью инструментов командной строки

У меня есть файл Apache access.log, размер которого составляет около 35 ГБ. Пролистать это уже не вариант, без особого ожидания.

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

Дата в формате [15/Oct/2011:12:02:02 +0000], Любая идея, как я мог бы сделать это, используя только сценарии bash, стандартные программы для работы с текстом (grep, awk, sed и лайки), конвейерную передачу и перенаправление?

Имя входного файла access.log, Я хотел бы, чтобы выходные файлы имели такой формат, как access.apache.15_Oct_2011.log (это бы сработало, хотя и не очень приятно при сортировке.)

7 ответов

Решение

Один из способов использования awk:

awk 'BEGIN {
    split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ", months, " ")
    for (a = 1; a <= 12; a++)
        m[months[a]] = a
}
{
    split($4,array,"[:/]");
    year = array[3]
    month = sprintf("%02d", m[array[2]])

    print > FILENAME"-"year"_"month".txt"
}' incendiary.ws-2009

Это выведет файлы как:

incendiary.ws-2010-2010_04.txt
incendiary.ws-2010-2010_05.txt
incendiary.ws-2010-2010_06.txt
incendiary.ws-2010-2010_07.txt

Для файла журнала 150 МБ ответ от chepner занял 70 секунд на 8-ядерном Xeon E31270 с частотой 3,4 ГГц, а этот метод занял 5 секунд.

Оригинальное вдохновение: " Как разделить существующий лог-файл apache по месяцам?"

Чистый Bash, делая один проход через журнал доступа:

while read; do
    [[ $REPLY =~ \[(..)/(...)/(....): ]]

    d=${BASH_REMATCH[1]}
    m=${BASH_REMATCH[2]}
    y=${BASH_REMATCH[3]}

    #printf -v fname "access.apache.%s_%s_%s.log" ${BASH_REMATCH[@]:1:3}
    printf -v fname "access.apache.%s_%s_%s.log" $y $m $d

    echo "$REPLY" >> $fname
done < access.log

Вот awk версия, которая выводит лексически сортируемые файлы журнала.

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

awk -F"[]/:[]" '
BEGIN {
  m2n["Jan"] =  1;  m2n["Feb"] =  2; m2n["Mar"] =  3; m2n["Apr"] =  4;
  m2n["May"] =  5;  m2n["Jun"] =  6; m2n["Jul"] =  7; m2n["Aug"] =  8;
  m2n["Sep"] =  9;  m2n["Oct"] = 10; m2n["Nov"] = 11; m2n["Dec"] = 12;
}
{
  if($4 != pyear || $3 != pmonth || $2 != pday) {
    pyear  = $4
    pmonth = $3
    pday   = $2

    if(fname != "")
      close(fname)

    fname  = sprintf("access_%04d_%02d_%02d.log", $4, m2n[$3], $2)
  }
  print > fname
}' access-log

Perl пришел на помощь:

cat access.log | perl -n -e'm@\[(\d{1,2})/(\w{3})/(\d{4}):@; open(LOG, ">>access.apache.$3_$2_$1.log"); print LOG $_;'

Ну, это не совсем "стандартная" программа манипуляции, но, тем не менее, она предназначена для манипулирования текстом.

Я также изменил порядок аргументов в имени файла, чтобы файлы были названы как access.apache.yyyy_mon_dd.log для упрощения сортировки.

Я объединил решения Theodore и Thor для повышения эффективности Thor и ежедневных файлов, но сохранил первоначальную поддержку адресов IPv6 в объединенном формате.

awk '
BEGIN {
  m2n["Jan"] =  1;  m2n["Feb"] =  2; m2n["Mar"] =  3; m2n["Apr"] =  4;
  m2n["May"] =  5;  m2n["Jun"] =  6; m2n["Jul"] =  7; m2n["Aug"] =  8;
  m2n["Sep"] =  9;  m2n["Oct"] = 10; m2n["Nov"] = 11; m2n["Dec"] = 12;
}
{
  split($4, a, "[]/:[]")
  if(a[4] != pyear || a[3] != pmonth || a[2] != pday) {
    pyear  = a[4]
    pmonth = a[3]
    pday   = a[2]

    if(fname != "")
      close(fname)

    fname  = sprintf("access_%04d-%02d-%02d.log", a[4], m2n[a[3]], a[2])
  }
  print >> fname
}'

Вид уродлив, это для тебя

    for year in 2010 2011 2012; do
       for month in jan feb mar apr may jun jul aug sep oct nov dec; do
           for day in 1 2 3 4 5 6 7 8 9 10 ... 31 ; do
               cat access.log | grep -i $day/$month/$year > $day-$month-$year.log
            done
        done
     done

Я немного улучшил ответ Теодора, чтобы видеть прогресс при обработке очень большого файла журнала.

#!/usr/bin/awk -f

BEGIN {
    split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ", months, " ")
    for (a = 1; a <= 12; a++)
        m[months[a]] = a
}
{
    split($4, array, "[:/]")
    year = array[3]
    month = sprintf("%02d", m[array[2]])

    current = year "-" month
    if (last != current)
        print current
    last = current

    print >> FILENAME "-" year "-" month ".txt"
}

Также я обнаружил, что мне нужно использовать gawk (brew install gawk если у вас его нет), чтобы это работало на Mac OS X.

Другие вопросы по тегам