基于重复模式和行数

时间:2018-03-15 22:04:12

标签: linux bash awk

我有一个系统生成非常大的文本日志(每个超过1GB)。我正在为它们提供的实用程序要求每个文件小于500MB。我不能简单地使用split命令,因为这会冒一个将日志条目分成两半的风险,这会导致它们被输入的实用程序出错。

我对split,csplit和awk做了一些研究。到目前为止,我运气最好的是:

awk '/REG_EX/{if(NR%X >= (X-Y) || NR%2000 <= Y)x="split"++i;}{print > x;}' logFile.txt

在上面的示例中,X表示我希望每个拆分文件包含的行数。在实践中,这最终约为1000万。 Y代表“加或减”。因此,如果我想要“1000万加或减50”,Y允许这样做。

我使用的实际正则表达式并不重要,因为该部分有效。目标是每隔X行拆分文件,但前提是它只是REG_EX的出现。这是if()子句的用武之地。我试图有一些正或负Y线的“摆动空间”,因为无法保证REG_EX将完全存在于NR%X处。我的问题是,如果我将Y设置得太小,那么我最终得到的文件数量是我瞄准的行数的两到三倍。如果我将Y设置得太大,那么我最终会得到一些包含1到X行之间的文件(REG_EX可能会连续发生几次)。

如果没有编写我自己的程序来逐行遍历文件计数器,我怎样才能优雅地解决这个问题呢?我有一个同事创建的脚本,但需要一个多小时才能完成。我的awk命令在1.5GB文件上以不到60秒的时间完成,X值为1000万,但不是100%的解决方案。

==编辑==

找到解决方案。感谢所有花时间阅读我的问题,理解并提供建议解决方案的人。他们中的大多数都非常有帮助,但我标记为解决方案的人提供了最大的帮助。我的问题是我的模块化数学是截止点。我需要一种方法来跟踪行并在每次分割文件时重置计数器。作为awk的新手,我不确定如何使用BEGIN{ ... }功能。让我总结一下问题集,然后列出解决问题的命令。

问题:
- 系统生成文本日志&gt; 1.5GB
- 输入日志的系统需要日志<= 500MB - 每个日志条目都以标准化行开头 - 使用split命令会冒一个新文件的开头没有标准行

要求:
- 在第X行分割文件,但是 - IFF Xth行采用标准日志条目格式

注意:
- 日志条目的长度各不相同,有些是完全空的

解决方案:

    awk 'BEGIN {min_line=10000000; curr_line=1; new_file="split1"; suff=1;} \
    /REG_EX/ \
    {if(curr_line >= min_line){new_file="split"++suff; curr_line=1;}} \
    {++curr_line; print > new_file;}' logFile.txt

该命令可以在一行输入;为了便于阅读,我把它分解了。一千万行可以达到450MB到500MB之间。我意识到,考虑到标准日志输入行的出现频率,我不需要设置上限限制,只要我选择了一个有限空间的下限。每次REG_EX匹配时,它会检查当前行数是否大于我的限制,如果是,则启动一个新文件并重置我的计数器。

再次感谢大家。我希望碰到这个或类似问题的其他任何人都觉得这很有用。

4 个答案:

答案 0 :(得分:1)

如果要根据模式出现的确切n次计数创建拆分文件,可以这样做:

awk '/^MYREGEX/ {++i; if(i%3==1){++j}} {print > "splitfilename"j}' logfile.log

其中:

  • ^MYREGEX是您想要的模式。
  • 3是模式的计数 你想要在每个文件中出现。
  • splitfilename是前缀 要创建的文件名。
  • logfile.log是您的输入日志文件。
  • i是一个计数器,每次出现模式时都会递增。
  • j是一个计数器,对于每个第n次出现的模式递增。

示例:

$ cat test.log
MY
123
ksdjfkdjk
MY
234
23
MY
345
MY
MY
456
MY
MY
xyz
xyz
MY
something

$ awk '/^MY/ {++i; if(i%3==1){++j}} {print > "file"j}' test.log

$ ls
file1  file2  file3  test.log

$ head file*
==> file1 <==
MY
123
ksdjfkdjk
MY
234
23
MY
345

==> file2 <==
MY
MY
456
MY

==> file3 <==
MY
xyz
xyz
MY
something

答案 1 :(得分:0)

您可能会将日志文件拆分1000万行。 然后,如果第二个分割文件没有以所需的行开始,则在第一个分割文件中找到最后一个所需的行,从那里删除该行和后续行,然后将这些行添加到第二个文件。 对每个后续拆分文件重复上述操作。

这将生成与正则表达式匹配计数非常相似的文件。

为了提高性能而不必实际编写中间拆分文件并对其进行编辑,您可以使用pt-fifo-split之类的工具来实现&#34;虚拟&#34;拆分原始日志文件。

答案 2 :(得分:0)

如果基于正则表达式的拆分不重要,一个选项是逐行创建新文件,跟踪要添加到输出文件的字符数。如果字符数大于某个阈值,则可以开始输出到下一个文件。示例命令行脚本是:

cat logfile.txt | awk 'BEGIN{sum=0; suff=1; new_file="tmp1"} {len=length($0); if ((sum + len) > 500000000) { ++suff; new_file = "tmp"suff; sum = 0} sum += len; print $0 > new_file}'

在此脚本中,sum会跟踪我们从给定日志文件中解析的字符数。如果sum在500 MB范围内,则会一直输出到tmp1。一旦sum即将超过该限制,它将开始输出到tmp2,依此类推。

此脚本不会创建大于限制大小的文件。它也不会破坏日志条目。

请注意,此脚本不会使用您在脚本中使用的任何模式匹配。

答案 3 :(得分:0)

foutslimit值替换为您的需求

#!/bin/bash

# big log filename
f="test.txt"
fout="$(mktemp -p . f_XXXXX)"
fsize=0
slimit=2500

while read line; do
    if [ "$fsize" -le "$slimit" ]; then
        # append to log file and get line size at the same time ;-)
        lsize=$(echo "$line" | tee -a $fout | wc -c)
        # add to file size
        fsize=$(( $fsize + $lsize ))
    else
        echo "size of last file $fout: $fsize"
        # create a new log file
        fout="$(mktemp -p . f_XXXXX)"
        # reset size counter
        fsize=0
    fi
done < <(grep 'YOUR_REGEXP' "$f")

size of last file ./f_GrWgD: 2537
size of last file ./f_E0n7E: 2547
size of last file ./f_do2AM: 2586
size of last file ./f_lwwhI: 2548
size of last file ./f_4D09V: 2575
size of last file ./f_ZuNBE: 2546