我有一个文件如下:
示例.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
(对于P
erl,也是一个非标准的 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 的行将被抑制,结果将是example01
和example03
为空,因此不会被创建。
答案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