为什么这个 sed 命令只适用于所有其他匹配?

时间:2021-01-30 03:13:57

标签: regex sed

这是一个 sed 命令,效果很好,只是在其他每一行(为了您的方便而简化):

<?php $headers = 'From: my@email.com' . "\r\n"; $headers .= 'Content-Type: text/plain; charset=UTF-8' . "\r\n"; if($_POST["message"]) { mail( "my@email.com", "Note To Self", $_POST["message"], $headers ); header( "Location: sent.html" ); } ?>

如果我的 testfile.txt 是

cat testfile.txt | sed -E "/PATTERN/,/^>/{//!d;}"

预期输出:

>PATTERN
1 
2
3

>PATTERN
a
b
c

>PATTERN
1 
2
3

>PATTERN
a
b
c

>asdf
1
2
3

>asdf
a
b
c

实际输出:

>PATTERN
>PATTERN
>PATTERN
>PATTERN
>asdf
1
2
3

>asdf
a
b
c

-一个助手-

(实际目标是找到一组模式中的一个,然后删除它后面的内容,直到下一次出现“>”符号{也删除我可以通过管道传递到 {{ 1}}})

我通过遵循我发现的 here 或多或少得到了指导。我已经为我完成了这项工作。这是一个确切的示例(不是您有要查看的文件)

>PATTERN
>PATTERN
a
b
c

>PATTERN
>PATTERN
a
b
c

>asdf
1
2
3

>asdf
a
b
c

4 个答案:

答案 0 :(得分:2)

/PATTERN/,/^>/ 将从包含 PATTERN 的行匹配到以 > 开头的行(可以是包含 PATTERN 的行)。您应该改为匹配一个空行,如下所示:

$ sed '/PATTERN/,/^$/{/PATTERN/!d}' ip.txt
>PATTERN
>PATTERN
>PATTERN
>PATTERN
>asdf
1
2
3

>asdf
a
b
c

你的旁白对我来说不是很清楚,但如果你也想删除带有 PATTERN 的行,你可以将其简化为:

$ sed '/PATTERN/,/^$/d' ip.txt
>asdf
1
2
3

>asdf
a
b
c

您也可以使用:

awk -v RS= -v ORS='\n\n' '!/PATTERN/'

但它在输出的末尾会有一个额外的空行。优点是您可以这样做,而不是您的 for 循环:

awk 'BEGIN{FS="\n"; ORS="\n\n"}
     NR==FNR{a[">" $0]; next}
     !($1 in a)' bad_results.txt RS= 16S.fasta

以上代码将 bad_results.txt 的每一行存储在一个关联数组中,以 > 字符为前缀。然后,仅当 16S.fasta 中不存在以 > 开头的整行时,才会打印 bad_results.txt 的内容。

如果您想要部分匹配:

awk 'BEGIN{FS="\n"; ORS="\n\n"}
     NR==FNR{a[$0]; next}
     {for (k in a) if(index($1, k)) next; print}' bad_results.txt RS= 16S.fasta

答案 1 :(得分:1)

这可能对你有用(GNU sed):

sed -E '/PATTERN/{p;:a;$!{N;/\n>/!s/\n//;ta};D}' file

如前所述,范围运算符匹配从 PATTERN 到以 > 开头的行。后一行也可能包含 PATTERN 但不匹配,因此是交替模式。

上述解决方案不使用范围运算符,而是收集从第一个包含 PATTERN 的行到以 > 开头的行之前的行。

如果一行包含 PATTERN,则将其打印出来,然后收集后续行,直到文件结束或一行开始 >

在这个集合中,换行符被删除 - 基本上使模式空间中的第一行成为一个或多个行的串联。

在匹配(或文件结尾)时,删除这一长行,并处理仍在模式空间中的任何行,就好像它已作为正常 sed 循环的一部分读入一样。

注意dD 命令之间的区别在于 d 命令删除模式空间并立即开始下一个 sed 循环,包括读取下一行输入。而 D 命令删除所有内容,直到并包括模式空间中的第一个换行符,然后开始下一个 sed 循环。但是,如果模式空间不为空,则放弃从输入读取下一行,然后恢复 sed 循环。

另一种选择:

sed '/^>/{h;/^>PATTERN/p};G;/\n>PATTERN/!P;d' file

答案 2 :(得分:1)

在您的范围模式匹配中,第二个元素“消耗”了该行,因此范围的开头不再将该块视为匹配项。这就是为什么你显然有“跳过”的原因。这可以通过使用不消耗字符进行匹配的前瞻来解决。不幸的是,sed 缺乏前瞻。

对于涉及前瞻的复杂多行匹配,

Perl 确实是比 sed 更好的选择。

这是一个 Perl,它读取文件并将正则表达式 /(?:^>PATTERN)|(?:^>[\s\S]*?)(?=\v?^>|\z)/ (Demo) 应用到它:

$ perl -0777 -lnE 'while(/(?:^>PATTERN)|(?:^>[\s\S]*?)(?=\v?^>|\z)/gm) { say $& }' file
>PATTERN
>PATTERN
>PATTERN
>PATTERN
>asdf
1
2
3

>asdf
a
b
c

旁白:请阅读Looping through the content of a file in Bash。你这样做的方式不是想法。具体来说,read here 在 Bash 循环中使用 cat 的副作用。

答案 3 :(得分:0)

回答关于为什么的问题,它似乎跳过了所有其他事件 (正如 Sundeep 回答的评论中所充实的那样。请参阅他的回答以解决此问题)

明显的跳跃只是一种错觉。 sed 是贪婪的;它找到了 PATTERN 的第一次出现,直到 并包括> 开头的下一行。然后它会删除(按照指示)之间的所有内容。 sed 然后从它停止的地方继续,因此不会“看到”最后一行作为新的出现

要清楚:

>PATTERN     <--- sed see's the first occurrence here------------------|
a                                                                      |(this whole
a                                                                      |chunk is
a                                                                      |considered
                                                                       |by sed)
>PATTERN     <--- then matches up to here (the next occurence of ">")--|
b            <--- then continues from here "missing" the match of PATTERN above
b
b

>PATTERN
c
c
c