【问题标题】:Arrange Log Entries into Dated Files将日志条目排列到日期文件中
【发布时间】:2009-10-28 18:47:09
【问题描述】:

我正在尝试拆分一个大型日志文件,一次包含几个月的日志条目,并且我正在尝试按日期将其拆分为日志文件。有几千行如下:

Sep 4 11:45 kernel: Entry
Sep 5 08:44 syslog: Entry

我正在尝试将其拆分,以便文件 logfile.20090904 和 logfile.20090905 包含条目。

我创建了一个程序来读取每一行,并将其发送到适当的文件,但运行速度很慢(尤其是因为我必须将月份名称转换为数字)。我想过每天都做一次 grep,这需要在文件中找到第一个日期,但这似乎也很慢。

有没有更优化的解决方案?也许我错过了一个更好的命令行程序。

这是我目前的解决方案:

#! /bin/bash
cat $FILE | while read line; do
  dts="${line:0:6}"
  dt="`date -d "$dts" +'%Y%m%d'`"
  # Note that I could do some caching here of the date, assuming
  # that dates are together.
  echo $line >> $FILE.$dt 2> /dev/null
done

【问题讨论】:

    标签: linux bash syslog


    【解决方案1】:

    @OP 尽量不要使用 bash 的 while 读取循环来迭代一个大文件。它经过试验证明它很慢,此外,您正在为您读取的文件的每一行调用外部日期命令。这是一种更有效的方法,只使用 gawk

    gawk 'BEGIN{
     m=split("Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec",mth,"|")     
    }
    { 
     for(i=1;i<=m;i++){ if ( mth[i]==$1){ month = i } }
     tt="2009 "month" "$2" 00 00 00" 
     date= strftime("%Y%m%d",mktime(tt))
     print $0 > FILENAME"."date
    }
    ' logfile
    

    输出

    $ more logfile
    Sep 4 11:45 kernel: Entry
    Sep 5 08:44 syslog: Entry
    
    $ ./shell.sh
    
    $ ls -1 logfile.*
    logfile.20090904
    logfile.20090905
    
    $ more logfile.20090904
    Sep 4 11:45 kernel: Entry
    
    $ more logfile.20090905
    Sep 5 08:44 syslog: Entry
    

    【讨论】:

    • 干得好...我想到了 sed,但我没有看到我在寻找什么,但这看起来很棒。
    【解决方案2】:

    考虑到您已经完成的操作,最快的方法是简单地将文件命名为“Sep 4”等,然后在最后将它们全部重命名 - 这样您所要做的就是读取一定数量的字符,无需额外处理。

    如果由于某种原因您不想这样做,但您知道日期是按顺序排列的,则可以在两种形式中缓存上一个日期,并进行字符串比较以确定是否需要再次运行 date或者只使用旧的缓存日期。

    最后,如果速度真的是个问题,你可以试试 perl 或 python 而不是 bash。不过,您在这里并没有做任何太疯狂的事情(除了在每一行开始一个子shell和日期处理,我们已经想出了如何避免),所以我不知道它会有多大帮助。

    【讨论】:

    • 处理 70,000 行仍然需要超过 15 秒,但 grep 需要 0.072 用户秒才能完成所有操作。我不知道 grep 怎么这么快。可能是编译的缘故吧。
    • grep 没有写入文件。您正在打开和关闭文件以写入 70000 次。 idimba 的回答有助于解决问题的这一方面——尽管我认为那时我会切换语言。编写打开和关闭似乎比使用文件描述符更容易。
    • 啊,这很有道理。这在我看过的 BASH 教程中从来没有提到过,或者我可能只是跳过了它......
    • 这不是任何人都想提及的事情。由于您不必手动打开文件进行写入,它显然是为您完成的,并且由于您没有打开它,因此关闭必须是自动的,因此必须在写入完成后进行。不过,上面的 gawk 答案会快很多。请为有用的答案投票并接受您使用/最有帮助的答案!
    • 啊,真的。我不能投票,因为我只有 11 分,需要 15 分。不过我确实接受了。
    【解决方案3】:

    脚本框架:

    BIG_FILE=big.txt
    
    # remove $BIG_FILE when the script exits
    trap "rm -f $BIG_FILE" EXIT
    
    cat $FILES > $BIG_FILE || { echo "cat failed"; exit 1 }
    
    # sort file by date in place
    sort -M $BIG_FILE -o $BIG_FILE || { echo "sort failed"; exit 1 }
    
    while read line;
       # extract date part from line ...
       DATE_STR=${line:0:12} 
    
       # a new date - create a new file
       if (( $DATE_STR != $PREV_DATE_STR)); then 
           # close file descriptor of "dated" file
           exec 5>&- 
           PREV_DATE_STR=$DATE_STR
    
           # open file of a "dated" file for write
           FILE_NAME= ... set to file name ...
           exec 5>$FILE_NAME || { echo "exec failed"; exit 1 }
       fi
    
       echo -- $line >&5 || { echo "print failed"; exit 1 }
    done < $BIG_FILE
    

    【讨论】:

    • 你说的这个“&-”魔法是什么?我从未见过这些文件描述符。
    • 关闭文件描述符。例如“find / 2>&-”将抑制 find 打印到 stderr 的所有消息。见linuxtopia.org/online_books/advanced_bash_scripting_guide/…
    • 我明白了。 Bash 通常很容易理解,但这些奇怪的事情有时会让我陷入困境。我将尝试实施,看看我得到什么样的时机。谢谢。
    • 这些有点更高级的东西,但它并不是为了满足您的需要而复杂化。非常酷的东西可以用它来实现。等待听到结果:)
    • 嘿,“return”需要更改为“exit”,因为它不在函数中,并且 print 抱怨 -u 不是有效参数。那是错字吗?我可以使用echo $line &gt; 5 对吗?
    【解决方案4】:

    这个脚本执行内部循环 365 或 366 次,一年中的每一天一次,而不是遍历日志文件的每一行:

    #!/bin/bash
    month=0
    months=(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
    for eom in 31 29 31 30 31 30 31 31 30 31 30 31
    do
        (( month++ ))
        echo "Month $month"
        if (( month == 2 ))    # see what day February ends on
        then
            eom=$(date -d "3/1 - 1 day" +%-d)
        fi
        for (( day=1; day<=eom; day++ ))
        do
            grep "^${months[$month - 1]} $day " dates.log > temp.out
            if [[ -s temp.out ]]
            then
                mv temp.out file.$(date -d $month/$day +"%Y%m%d")
            else
                rm temp.out
            fi
            # instead of creating a temp file and renaming or removing it,
            # you could go ahead and let grep create empty files and let find
            # delete them at the end, so instead of the grep and if/then/else
            # immediately above, do this:
            # grep --color=never "^${months[$month - 1]} $day " dates.log > file.$(date -d $month/$day +"%Y%m%d")
        done
    done
    # if you let grep create empty files, then do this:
    # find -type f -name "file.2009*" -empty -delete
    

    【讨论】:

    • 月末这很有创意,但不是我希望的方式。在我看来,grep 效率低下,即使结果似乎更快。
    猜你喜欢
    • 1970-01-01
    • 2017-06-03
    • 2011-12-04
    • 1970-01-01
    • 1970-01-01
    • 2014-11-17
    • 2014-12-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多