多行 grep 搜索每次出现的单独文件

多行 grep 搜索每次出现的单独文件

我有一个文件如下:

示例.txt

    -1
    15
         1         0         0        11 -1.0000E+001  1.0000E+001 -1.0000E+001
         2         0         0        11  1.0000E+001  1.0000E+001 -1.0000E+001
...
        29         0         0        11  1.0000E+001  2.0000E+001  1.0000E+001
        30         0         0        11  5.0000E+000  5.0000E+000  5.0000E+000
    -1
 
#ffafsda
    -1
    780
         1       116         1         2         1         1         7        20
         1        11         2        15         4        18         3        12
        13        16        22        19         5        24         9        29
         8        27         6        23
    -1
    
    asfasd
    
    afsdasdf

它由始终以完全匹配的行开始和结束的块组成^ {4}-1$。我需要通过这些块将一个文件分成多个。

我现在想到的是提取这些块的多行正则表达式:

grep -Pzo '(?s)((?m:^)\s{4}-1(?m:$).*?(?m:^)\s{4}-1(?m:$))' example.txt

输出:

    -1
    15
         1         0         0        11 -1.0000E+001  1.0000E+001 -1.0000E+001
         2         0         0        11  1.0000E+001  1.0000E+001 -1.0000E+001
...
        29         0         0        11  1.0000E+001  2.0000E+001  1.0000E+001
        30         0         0        11  5.0000E+000  5.0000E+000  5.0000E+000
    -1    -1
    780
         1       116         1         2         1         1         7        20
         1        11         2        15         4        18         3        12
        13        16        22        19         5        24         9        29
         8        27         6        23
    -1

您看到第二个匹配项完全打印在第一个匹配项后面(没有换行符或分隔符) - 我无法将这些事件分离到文件中

所需的输出如下:

文件1:

    -1
    15
         1         0         0        11 -1.0000E+001  1.0000E+001 -1.0000E+001
         2         0         0        11  1.0000E+001  1.0000E+001 -1.0000E+001
...
        29         0         0        11  1.0000E+001  2.0000E+001  1.0000E+001
        30         0         0        11  5.0000E+000  5.0000E+000  5.0000E+000
    -1

文件2

    -1
    780
         1       116         1         2         1         1         7        20
         1        11         2        15         4        18         3        12
        13        16        22        19         5        24         9        29
         8        27         6        23
    -1

任何帮助表示赞赏。

答案1

使用-z(非标准 GNU 扩展),grep适用于 NUL 分隔记录,它不是多行grep,所以:

  • 匹配是在每个 NUL 分隔记录上独立完成的,如果没有分隔,则在整个输入上完成(使用非分隔记录的能力是另一个 GNU 扩展)
  • -o另一个非标准 GNU 扩展)每个匹配项都输出 NUL 分隔的

所以你的输出中的记录分开(实际上分隔的)。sed -n l例如,如果您传递输出,您可以看到:

$ grep -Pzo '(?s)((?m:^)\s{4}-1(?m:$).*?(?m:^)\s{4}-1(?m:$))' example.txt | sed -n l
    -1$
    15$
         1         0         0        11 -1.0000E+001  1.0000E+001 -1\
.0000E+001$
         2         0         0        11  1.0000E+001  1.0000E+001 -1\
.0000E+001$
...$
        29         0         0        11  1.0000E+001  2.0000E+001  1\
.0000E+001$
        30         0         0        11  5.0000E+000  5.0000E+000  5\
.0000E+000$
    -1\000    -1$
    780$
         1       116         1         2         1         1         \
7        20$
         1        11         2        15         4        18         \
3        12$
        13        16        22        19         5        24         \
9        29$
         8        27         6        23$
    -1\000$

请参阅\000分隔每个匹配项的 s。

在这里你可以简化你的匹配:

grep -Pzo '(?sm)(^\s{4}-1$).*?(?1)' example.txt

但与其使用grep-P(对于Perl,也是一个非标准的 GNU 扩展),您可以使用真实的东西,它有几个优点:

  • 更易于移植,因为 perl 比 GNU grep 存在于更多的系统上(并且类似 perl 的正则表达式支持并不总是在 GNU 构建时启用grep
  • perl 必须-0使用 NUL 分隔的记录,但这不是您想要的。你想要一个 slurp 模式,它perl-0777
  • perl 可以自行将输出写入单独的文件:
perl -l -0777 -ne '
  while (/(^\s{4}-1$).*?(?1)/msg) {
    open OUT, ">", "output-" . ++$n . ".txt" or die;
    print OUT $&
  }' example.txt

或者,不要将整个文件作为一个整体并使用正则表达式,而是逐行读取它:

perl -ne '
  if (/^\s{4}-1$/) {
    if ($inside = 1 - $inside) {
      open OUT, ">", "output-" . ++$n . ".txt" or die;
    } else {
      print OUT; next
    }
  }
  print OUT if $inside' example.txt

(尽管如果不全部匹配,则会给出不同的结果-1)。


1 为此,请参阅pcre2grep -M(以前的pcregrep -M),pcre2grep这是一个随 PCRE2 一起提供的示例应用程序,GNUgrep使用(可以使用)作为其-P选项。

答案2

另一种获取整个块的方法而不是grep

首先,我建议使用来sed创建

sed -n '/^ \{4\}-1$/,/^ \{4\}-1$/p' example.txt
    -1
    15
         1         0         0        11 -1.0000E+001  1.0000E+001 -1.0000E+001
         2         0         0        11  1.0000E+001  1.0000E+001 -1.0000E+001
...
        29         0         0        11  1.0000E+001  2.0000E+001  1.0000E+001
        30         0         0        11  5.0000E+000  5.0000E+000  5.0000E+000
    -1
    -1
    780
         1       116         1         2         1         1         7        20
         1        11         2        15         4        18         3        12
        13        16        22        19         5        24         9        29
         8        27         6        23
    -1

将块分割到不同的文件

然后你可以使用csplit命令根据模式分割文件。

姓名

csplit- 将文件分割为由上下文行确定的部分

概要

csplit[选项]...文件模式...

描述

将由 PATTERN 分隔的 FILE 片段输出到文件“xx00”、“xx01”...,并将每个片段的字节计数输出到标准输出。

例子

$ sed -n '/^ \{4\}-1$/,/^ \{4\}-1$/p' example.txt | csplit - -f example --suppress-matched -z '/^ \{4\}-1$/' '{*}'
331
292

解释:

  • csplit -- 将从标准输入读取
  • -f example- 将文件的前缀设置为“example”(而不是默认的“xx”。每个前缀后跟一个从 00 开始的两位数字。
  • --suppress-matched- 抑制与模式 ( ) 匹配的行/^ \{4\}-1$/
    • 这是需要的,因为csplit按模式执行分割(你不能告诉它第一行和最后一行,只有一个模式),所以在每个“关闭”模式之后,它将创建一个仅包含该模式的文件(因为在下面它会再次分裂)。如果您抑制该模式,则可以通过下一个标志来避免这种情况:
  • -z- 删除空输出文件
  • '/^ \{4\}-1$/'- 模式指示分割文件的位置。
  • '{*}'- 尽可能多次重复之前的模式

它将输出它创建的每个文件的大小。

结果:2 个文件具有所需的块,但没有模式。

$ cat example00
    15
         1         0         0        11 -1.0000E+001  1.0000E+001 -1.0000E+001
         2         0         0        11  1.0000E+001  1.0000E+001 -1.0000E+001
...
        29         0         0        11  1.0000E+001  2.0000E+001  1.0000E+001
        30         0         0        11  5.0000E+000  5.0000E+000  5.0000E+000

$ cat example01
    780
         1       116         1         2         1         1         7        20
         1        11         2        15         4        18         3        12
        13        16        22        19         5        24         9        29
         8        27         6        23

如果要将分隔行返回到文件(-1第一行和最后一行),可以使用以下命令:

sed -i '1s/.*/    -1\n\0/; $s/$/\n    -1/' example[0-9][0-9]

--suppress-matched关于和-z标志的进一步解释

为了解释 的必要性--suppress-matched,我将向您展示会发生什么

$ sed -n '/^ \{4\}-1$/,/^ \{4\}-1$/p' example.txt | csplit -f example  -z - '/^ \{4\}-1$/' '{*}'
338
7
299
7

它创建了 4 个文件。请注意,example01并且example03仅包含模式。

$ cat example00
    -1
    15
         1         0         0        11 -1.0000E+001  1.0000E+001 -1.0000E+001
         2         0         0        11  1.0000E+001  1.0000E+001 -1.0000E+001
...
        29         0         0        11  1.0000E+001  2.0000E+001  1.0000E+001
        30         0         0        11  5.0000E+000  5.0000E+000  5.0000E+000

$ cat example01
    -1

$ cat example02
    -1
    780
         1       116         1         2         1         1         7        20
         1        11         2        15         4        18         3        12
        13        16        22        19         5        24         9        29
         8        27         6        23

$ cat example03
    -1

当使用 时--suppress-matched,带 -1 的行将被抑制,结果将是example01example03为空,因此不会被创建。

答案3

您可以使用 GNU awk,它允许将正则表达式用作记录分隔符,作为定义“行”的东西。在这里,我们可以将其设置为\n -1\n,即一个换行符、4 个空格-1和一个换行符。然后,由于它出现在您想要的部分的开头和结尾,因此我们本质上需要每隔一个“行”,因此我们可以在行号模 2 为 0 时打印:

gawk '
  BEGIN{
    RS="\n    -1\n"; 
    ORS=RS
  } 
  NR % 2 ==0 { print RS $0 > "outfile." ++c }' file 

在您的示例上运行上述命令会生成两个包含以下内容的文件:

$ ls
file  outfile.1  outfile.2
$ cat outfile.1

    -1
    15
         1         0         0        11 -1.0000E+001  1.0000E+001 -1.0000E+001
         2         0         0        11  1.0000E+001  1.0000E+001 -1.0000E+001
...
        29         0         0        11  1.0000E+001  2.0000E+001  1.0000E+001
        30         0         0        11  5.0000E+000  5.0000E+000  5.0000E+000
    -1
$ cat outfile.2

    -1
    780
         1       116         1         2         1         1         7        20
         1        11         2        15         4        18         3        12
        13        16        22        19         5        24         9        29
         8        27         6        23
    -1

这确实有一个不幸的副作用,即在每个文件的开头添加一个空行。如果这是一个问题,您可以直接打印-1明确的内容:

gawk '
  BEGIN{
    RS="\n    -1\n"; 
  } 
  NR % 2 ==0 { printf "   -1\n%s\n    -1\n", $0 > "outfile." ++c }' file 

答案4

使用任何 awk:

$ cat tst.awk
/^    -1/ {
    if ( inBlock ) {
        print > out; close(out)
    }
    else {
        out = FILENAME "_" (++cnt)
    }
    inBlock = !inBlock
}
inBlock { print > out }

$ awk -f tst.awk example.txt

$ head example.txt_*
==> example.txt_1 <==
    -1
    15
         1         0         0        11 -1.0000E+001  1.0000E+001 -1.0000E+001
         2         0         0        11  1.0000E+001  1.0000E+001 -1.0000E+001
...
        29         0         0        11  1.0000E+001  2.0000E+001  1.0000E+001
        30         0         0        11  5.0000E+000  5.0000E+000  5.0000E+000
    -1

==> example.txt_2 <==
    -1
    780
         1       116         1         2         1         1         7        20
         1        11         2        15         4        18         3        12
        13        16        22        19         5        24         9        29
         8        27         6        23
    -1

相关内容