19

我有一个 Apache access.log 文件,大小约为 35GB。通过它不再是一种选择,无需等待很多。

我想通过使用日期作为拆分标准将其拆分为许多小文件。

日期为格式[15/Oct/2011:12:02:02 +0000]。知道如何仅使用 bash 脚本、标准文本操作程序(grep、awk、sed 和 likes)、管道和重定向来做到这一点吗?

输入文件名为access.log. 我希望输出文件具有如下格式access.apache.15_Oct_2011.log(这可以解决问题,尽管在排序时不太好。)

4

7 回答 7

22

一种使用方式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]] = sprintf("%02d", a)
}
{
    split($4,array,"[:/]")
    year = array[3]
    month = 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 MB 的日志文件,chepner 在 3.4 GHz 8 Core Xeon E31270 上的回答耗时 70 秒,而此方法耗时 5 秒。

原始灵感:“如何按月拆分现有的apache日志文件?

于 2012-07-30T01:14:29.180 回答
10

纯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
于 2012-07-27T13:44:26.670 回答
4

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 以便于排序。

于 2012-07-27T12:38:46.857 回答
4

这是一个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
于 2012-07-27T14:26:46.243 回答
3

我结合了 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
}'
于 2015-01-20T21:42:57.060 回答
1

有点丑,这对你来说很糟糕:

    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
于 2012-07-27T12:45:28.893 回答
0

我对 Theodore 的回答做了些许改进,因此在处理非常大的日志文件时可以看到进度。

#!/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"
}

我还发现我需要使用gawkbrew install gawk如果你没有的话)它才能在 Mac OS X 上运行。

于 2014-05-01T05:15:11.987 回答