在单次迭代中提取相同模式的第一次和最后一次出现之间的所有内容

时间:2018-06-06 19:27:32

标签: perl unix awk sed grep

这个问题与this非常相似,只是我希望尽快做到这一点,只做一次(不幸的是gzip压缩)文件。

给定模式CAPTURE和输入

1:.........
...........
100:CAPTURE
...........
150:CAPTURE
...........
200:CAPTURE
...........
1000:......

打印:

100:CAPTURE
...........
150:CAPTURE
...........
200:CAPTURE

这可以用正则表达式完成吗?

我依稀记得这种语法不能通过正则表达式捕获,但不太确定正规表达式如今提供了展望等等。

7 个答案:

答案 0 :(得分:2)

我会写

gunzip -c file.gz | sed -n '/CAPTURE/,$p' | tac | sed -n '/CAPTURE/,$p' | tac

答案 1 :(得分:2)

您可以缓冲线条,直到看到包含CAPTURE的行,特别处理第一次出现的图案。

#!/usr/bin/env perl
use warnings;
use strict;

my $first=1;
my @buf;
while ( my $line = <> ) {
    push @buf, $line unless $first;
    if ( $line=~/CAPTURE/ ) {
        if ($first) {
            @buf = ($line);
            $first = 0;
        }
        print @buf;
        @buf = ();
    }
}

通过zcat file.gz | perl script.pl将输入信息输入此程序。

如果需要的话,当然可以将其塞进单线中......

zcat file.gz | perl -ne '$x&&push@b,$_;if(/CAPTURE/){$x||=@b=$_;print@b;@b=()}'
  

这可以用正则表达式完成吗?

你的意思是一次性使用一个正则表达式?如果你不介意将整个文件读入内存,请确保......但对于大文件来说,这显然是

zcat file.gz | perl -0777ne '/((^.*CAPTURE.*$)(?s:.*)(?2)(?:\z|\n))/m and print $1'

答案 2 :(得分:2)

找到第一个CAPTURE并回顾最后一个。

echo "/CAPTURE/,?CAPTURE? p" | ed -s <(gunzip -c inputfile.gz)

编辑:回答评论和第二(更好?)解决方案。

当您的输入未以换行符结束时,ed会抱怨,如这些测试所示。

# With newline
printf "1,$ p\n" | ed -s <(printf "%s\n" test)
# Without newline
printf "1,$ p\n" | ed -s <(printf "%s" test)
# message removed
printf "1,$ p\n" | ed -s <(printf "%s" test) 2> /dev/null

我不知道这会给大文件带来的内存复杂性,但你更喜欢流媒体解决方案 您可以使用sed进行下一步。 继续读行,直到找到第一场比赛。在此期间,只记住读取的最后一行(将其放入保留区域) 现在改变你的策略 将每一行附加到保留区域。你不知道何时冲到下一场比赛。
当您进行下一场比赛时,请调出保持区域并打印出来 我需要一些tweeking来防止第二场比赛打印两次。我通过读取下一行并用该行替换HOLD区域来解决这个问题 总解决方案是

gunzip -c inputfile.gz | sed -n '1,/CAPTURE/{h;n};H;/CAPTURE/{x;p;n;h};'

当您不喜欢sed持有空间时,您可以使用awk实现相同的方法:

gunzip -c inputfile.gz | 
   awk '/CAPTURE/{capt=1} capt==1{a[i++]=$0} /CAPTURE/{for(j=0;j<i;j++) print a[j]; i=0}'

答案 3 :(得分:0)

我不认为正则表达式会比双重扫描更快......

这是awk解决方案(双重扫描)

$ awk '/pattern/ && NR==FNR {a[++f]=NR; next} a[1]<=FNR && FNR<=a[f]' file{,} 

或者,如果您有关于模式在文件中出现位置的任何先验信息,您可以使用启发式方法,这些方法在这些特殊情况下会更快。

答案 4 :(得分:0)

这是另一个使用正则表达式的例子(缺点是,如果文件很大,它将占用大量内存)

import { AnimatorModule } from 'css-animator';

@NgModule({
  declarations: [
    MyApp,
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    HttpClientModule,
    AppConfigModule,
    IonicStorageModule.forRoot(),
    AnimatorModule
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
  ],
  providers: [
    StatusBar,
    SplashScreen,
    NativeStorage,
    { provide: ErrorHandler, useClass: IonicErrorHandler },
  ]
})

或使用一个班轮:

#!/usr/bin/perl
{
  local $/ = undef;
  open FILE, $ARGV[0] or die "Couldn't open file: $!";
  binmode FILE;
  $string = <FILE>;
  close FILE;
}

print $1 if $string =~ /([^\n]+(CAPTURE).*\2.*?)\n/s;

结果:

cat file.tmp | perl -ne '$/=undef; print $1 if <STDIN> =~ /([^\n]+(CAPTURE).*\2.*?)\n/s'

答案 5 :(得分:0)

这可能适合你(GNU sed):

sed '/CAPTURE/!d;:a;n;:b;//ba;$d;N;bb' file

删除所有行,直到第一行包含所需的字符串。打印包含所需字符串的行。用下一行替换模式空间。如果此行包含所需的字符串,请重复前两个句子。如果它是文件的最后一行,请删除模式空间。否则,追加下一行并重复前三句。

研究了用于haukex基准测试的测试文件后,似乎sed不是提取此文件的工具。使用csplit,grep和sed的混合物提供了一个相当快速的解决方案如下:

lines=$(grep -nTA1 --no-group-separator CAPTURE oldFile | 
        sed '1s/\t.*//;1h;$!d;s/\t.*//;H;x;s/\n/ /')
csplit -s oldFile $lines && rm xx0{0,2} && mv xx01 newFile

将原始文件拆分为三个文件。第一次出现CAPTURE之前的文件,从第一个CAPTURE到最后一个CAPTURE的文件以及包含其余文件的文件。第一个和第三个文件被丢弃,第二个文件被重命名。

csplit可以使用行号来拆分原始文件。 grep在过滤模式方面非常快,可以返回与CAPTURE和以下上下文行匹配的所有模式的行号。 sed可以将grep的结果操作为两个提供给csplit命令的行号。

当针对测试文件运行时(如上所述),我得到大约10秒的时间。

答案 6 :(得分:0)

在发布这个问题时,我遇到的问题是我有几个巨大的gzip压缩日志文件由java应用程序生成。 日志行具有以下格式:

[Timestamp] (AppName) {EventId} [INFO]: Log text...
[Timestamp] (AppName) {EventId} [EXCEPTION]: Log text...
                    at com.application.class(Class.java:154)
                    caused by......
[Timestamp] (AppName) {EventId} [LogLevel]: Log text...

给定EventId,我需要从这些文件中提取与事件对应的所有行。由于异常行可以是任意长度且不包含EventId,因此问题变得无法解决,因为EventId只有一个微不足道的grep。

不幸的是,我忘了考虑边缘情况,EventId的最后一个日志行可能是例外,这里发布的答案不会打印stacktrace行。然而,修改haukex的解决方案并不难以涵盖这些案例:

#!/usr/bin/env perl
use warnings;
use strict;

my $first=1;
my @buf;
while ( my $line = <> ) {
    push @buf, $line unless $first;
    if ( $line=~/EventId/ or ($first==0 and $line!~/\(AppName\)/)) {
        if ($first) {
            @buf = ($line);
            $first = 0;
        }
        print @buf;
        @buf = ();
    }
    else {
        $first = 1;
    }
}

我仍然想知道是否可以修改更快的解决方案(主要是沃尔特的sed解决方案或haukex的内存perl解决方案)来做同样的事情。