【问题标题】:Extract each match+context of +/-N rows even if contiguous/overlapping (grep/sed/awk/powershell/etc)即使连续/重叠(grep/sed/awk/powershell/etc),也提取+/-N行的每个匹配+上下文
【发布时间】:2020-10-15 20:43:43
【问题描述】:

我在使用 grep -B 10 -A 10 获取关键字 + 上下文行时遇到了意外问题。如果在该上下文中存在匹配,则它不会将其视为单独的匹配,而是扩展了上下文。之前遇到过这个问题:Do not merge the context of contiguous matches with grep

因此,当从以下位置查找匹配项时:

a
b
match1
c
d
e
match2
f
match3
g
h
i
j

我想得到(使用任何分隔符)。这里的示例是 N=2 在任一方向,但我想要一个易于调整数字的通用解决方案。

b
match1
c
d
--
d
e
match2
f
match3
--
match2
f
match3
g
h
--

有一个小python脚本作为答案编写,但考虑到现在已经9年了,也许有更好的方法。我正在处理大文件(100M+ 行),所以 python 脚本并不能真正完成这项工作。

也许有一些新的或旧的命令行工具可以做到这一点?

【问题讨论】:

  • 10 行就好了 ;)
  • 我建议您更改问题中的样本以反映这一点,这样做将为希望回答的人提供一种简单的测试方法。还有一个问题是您是否可以有多个重叠在这 10 行中,或者重叠限制为最多一个匹配
  • 啊,谢谢,在这种情况下,我确实想要一个通用的解决方案。我编辑了文本。多个重叠是可能的。只是想要每场比赛的上下文,无论其中有什么 - 也可能是其他比赛。但其他匹配不会扩展另一个匹配的上下文。
  • 我尝试了perl的解决方案,由于多个重叠案例,没有简单的解决方案。链接问题中的python解决方案将整个文件保存在数组中,这对大文件没有帮助。因此复杂性..速度将成为问题,除非您将其写在C/Rust/etc

标签: powershell sed command-line grep matching


【解决方案1】:

这是一个名为 context.plperl 脚本,其中 $n 控制上下文行的数量。

BEGIN { $/ = "\n"; $\ = ""; }
LINE: while (defined($_ = <ARGV>)) {
    sub BEGIN {
        $n = 3;
    }
    {
        $j = $. % $n;
        if (/match/) {
            for ($i = $j; $i < $j + $n; ++$i) {
                print $buf{$i % $n};
            }
            print $_;
            $fp = tell ARGV;
            foreach $_ (1 .. $n) {
                unless (eof) {
                    $line = <ARGV>;
                    print $line;
                }
            }
            print "--\n";
            seek ARGV, $fp, 0;
        }
        $buf{$j} = $_;
    }
}

该脚本通过具有大小为$n 的缓冲区来工作,该缓冲区保存先前的$n 行。只要有匹配,就会打印缓冲区内容,然后是当前行。然后,$fp 保存当前文件位置。然后,获取下一行$n 以进行打印,然后是分隔符--。然后恢复文件位置以从下一行开始处理到当前匹配行。

这是使用$n = 2 运行的示例:

$ perl context.pl ip.txt
a
b
match1
c
d
--
d
e
match2
f
match3
--
match2
f
match3
g
h
--

存在极端案例问题。如果文件在最后一次匹配后没有以 $n 行结尾,则结果会被破坏。例如,更改后的输入如下所示和$n = 4:

$ cat ip.txt
a
b
match1
match2
c
$ perl context.pl ip.txt
a
b
match1
match2
c
--
b
match1
a
match2
c
--

【讨论】:

    【解决方案2】:

    由于此处标记了 PowerShell,因此您可以执行以下操作,这些操作应该很容易占用内存,但会牺牲速度。我们可能会以其他更有效的方式读取文件,但您会失去一些简洁性。

    $StringMatch = 'match' # Text you want to match
    $n = 2 # Context number or the number of lines above and below the match
    $sectionEnd = $false
    $tracker = [collections.queue]::new()
    # You may want to feed in multiple files, which can be done with a surrounding foreach loop at this spot
    get-content a.txt -readcount 1 | foreach-object -Process {
        $sectionEnd = $false
        $tracker.Enqueue($_)
        if ($tracker.count -gt ($n*2+1)) {
            $null = $tracker.Dequeue()
        }
        if ($tracker.count -eq ($n*2+1) -and $tracker.ToArray()[$n] -match $StringMatch) {
            $tracker
            "----------"
            $sectionEnd = $true
        }
    } -End { # -End block can be removed if you don't want to output a final misaligned $tracker
        if (!$sectionEnd -and $tracker.ToArray() -match $StringMatch) {
            $tracker
            "---------"
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2020-08-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-02
      • 1970-01-01
      • 1970-01-01
      • 2019-04-19
      相关资源
      最近更新 更多