Perl 搜索和替换,直到对几行进行正面前瞻 - 没有按预期工作?

Perl search and replace until positive lookahead over several lines - not working as expected?

此处的总体目标是删除以特定字符串开头并以正前瞻结束的文本块。从我所做的测试来看,似乎换行符导致了问题,但我不确定到底发生了什么或修复它的最佳方法。

更多上下文:我想从 .fasta 文件中删除分类单元,包括分类单元名称和 header 信息以及相关序列。 (fasta 格式以 header >locusname-locusnumber-species_name |locusname-locusnumber \n 开头)。序列中缺失的数据被编码为“-”。最终我想对几个 species_names 执行此操作,并对目录中的数千个文件中的每一个执行此操作。

我认为这对于 bash (Ubuntu 18.04.2) 中的 perl one-liner 来说是一项简单的任务。 例如,从下面的摘录中,我想删除 Pseudomymrex seminole D1367 的整个序列,即以 >uce-483_Pseudomyrmex_seminole_D1367 |uce-483 开头并以>uce-483_Pseudomyrmex_seminole_D1435 之前的换行符。 . ..

为此,我有:perl -pe 's/>(.)+(Pseudomyrmex_seminole_D1367)[\s\S]+(?=>)//' infile.fasta > outfile.fasta

或等同于perl -pe 's/>(.)+(Pseudomyrmex_seminole_D1367(.)+(?=>)//s' infile.fasta > outfile.fasta

这两个似乎都没有任何效果(即 diff infile.fasta outfile.fasta 是空的。)如果我删除正面前瞻,它可以正常工作,但只能到第一个换行符。

以下是 .fasta 的上下文和测试摘录:

>uce-483_Pseudomyrmex_seminole_D1366 |uce-483
------------------------------------------------------------
---------------------------------------------------tgtaaacgt
tataatacatgcgtatgaaaaaaaaaagtgaacacccggtacgtacccgtgctgaaacgt
tcagatttacatccatttgtagtagcattttcgctagttttttcaagagcaaaaaggaca
cattcaaaactgaatatacatgtcacagatgtttgtttgtgtgcaggtacctgtaatttt
gcaaacatatacctatatatgtgtgtcgcatatatatcatgtagtagatttccatgttat
gcaacatcttctcacaatgacaatcggtcgtttccttcactccgaaatgttcatgcgaac
agttaatctatatcccaagcagcgatgtaatgttatgcggcgcgcaagtctcattagact
tgtaaaccgtccgagtttcgacttaccata----tgtgtgtgtgtgcgcgcgtatgtgca
cgtac------acacgtttgtttatacatttgtctatacatttgcgtgtgaacgcgggat
gaacagagatttgcgcacacatagacatgagaaacgtcacttgtcgatgtagatactaat
tgtggaaaatacatattcctcttcagatacacgggaatgttgaattattttcactcgctc
cacgcgcgagtgttcgctccttttacgcacaacgagtccttctgctgcagc--gagatag
aaaatatttttgcgcggtaatcgtaaacgtatgagtgcctttcgacgtgaattctcttat
ggcagttctcacggtgtaaattataatcgaattaacattgcgagtgtgatctcaatataa
ttatagcgtctaagaacaaacacgtaacatgcacacacacacacacacac----------
---
>uce-483_Pseudomyrmex_seminole_D1367 |uce-483
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
--ttcaaaactgaatatacatgtcacagatgtttgtttgtgtgcaggtacctgtaatttt
gcaaacatatg---atatatatgtgtcgcatatatatcatgtagtagatttccatgttat
gcaacatcttctcacaatgacaatcggtcgtttccttcactctgaaatgttcatgcgaac
agttaatctatatcccaagcagcgatgtaatgttatgcggcgcgcaagtctcattagact
tgtaaaccgtccgagtttcgacttaccata--tgtgtgtgtgtgtgtgcgcgtatgtgca
cgtacgcgcgcacacgtttgtttatacatttgtctatacatttgcgtgtgaacgcgggat
gaacagagatttgcgcacacatagacatgagaaacgtcacttgtcgatg-----------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
---
>uce-483_Pseudomyrmex_seminole_D1435 |uce-483
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
-------tacatccatttgtagtagcattttcgctagttttttcaagagcaaaaaggaca
cattcaaaactgaatatacatgtcacagatgtttgtttgtgtgcaggtacctgtaatttt
gcaaacatatacctatatatgtgtgtcgcatatatatcatgtagtagatttccatgttat
gcaacatcttctcacaatgacaatcggtcgtttccttcactccgaaatgttcatgcgaac
agttaatctatatcccaagcagcgatgtaatgttatgcggcgcgcaagtctcattagact
tgtaaaccgtccgagtttcgacttaccata--tgtgtgtgtgtgtgtgcgcgtatgtgca
cgtac------acacgtttgtttatacatttgtctatacatttgcgtgtgaacgcgggat
gaacagagatttgcgcacacatagacatgagaaacgtcacttgtcgatgtagatactaat
tgtggaaaatacatattcctcttcagatacacgggaa-----------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
------------------------------------------------------------
---

使用-p(或-n),一行代码一次读取一行;所以它不能匹配多行模式。一种解决方案是 "slurp" 整个文件,如果它不是太大(逐行解决方案见末尾

perl -0777 -pe'...' in > out   

参见 Command Switches in perlrun

然后,问题中显示的代码有一个不平衡的括号,无法编译。此外,没有理由捕获那些 .,所以去掉括号。接下来,模式

s/>.+Pseudomyrmex_seminole_D1367...//;

匹配从 very first > 到感兴趣的名称的所有内容,因此所有前面的序列也会被匹配和删除。相反,例如匹配 >[^>]+...D1367,因此 > 之后的所有不是 > 的内容都匹配到该短语。

最后,最后一个 .+(?=>) 将匹配所有内容到 最后一个 > 因此正则表达式将删除所有后续序列,而不是您想要的根据描述。相反,将其限制为与 > 之后的第一个匹配,方法是将其设置为 "non-greedy" 和 .+?(?=>),或者更简单地说,使用 [^>]+.

全部更正

perl -0777 -pe's/>[^>]+?Pseudomyrmex_seminole_D1367[^>]+//' in > out

请注意,现在不需要 /s 修饰符,因为它的目的是使 . 匹配换行符,这里我们不需要它,因为 [^>]也匹配换行符(除 > 之外的任何内容)。量词是 +? 以(希望)防止回溯每个不匹配的整个序列。

或者,使用您最初使用的前瞻

perl -0777 -pe's/>[^>]+?Pseudomyrmex_seminole_D1367.+?(?=>)//s' in > out

这些对您的示例以及我添加了更多序列 (>...) 的扩展示例都按预期工作。


作为参考,由于 fasta 文件可能太大而无法压缩成字符串,这里逐行显示。

一旦看到感兴趣的 >... 行,就设置一个标志;如果未设置该标志(并且如果我们不在该行上),则打印一行。一旦你到达下一个 > 清除标志(也打印该行)。

perl -ne'
    if (/^>.+?Pseudomyrmex_seminole_D1367/) { $f = 1 } 
    elsif (not $f) { print } 
    elsif (/^>/) { $f = 0; print }
' in > out

我怀疑这在非常大的文件上也可能表现得更好。

第一个解决方案中的正则表达式必须扫描每个序列整体才能发现它不是感兴趣的那个;只有当它遇到下一个 > 时,它才能确定序列不匹配(并且没有回溯,希望如此,因为如果遇到正确的短语,+? 会停止它) .

这里的代码主要检查第一个字符和一个标志。

所以这里的工作量要小得多 -- 但这里正则表达式引擎在 行启动,这很昂贵。如果不尝试,我无法自信地说出它们是如何相互叠加的。

您也可以使用 > 作为输入记录分隔符。这样你就可以避免吞噬整个文件,并且由于主循环逐块加载你的文件,你只需要测试哪个是不打印它的目标(无需以模式描述整个块):

perl -ln076e's/\n$//;print ">$_" if $_ && !/Pseudomyrmex_seminole_D1367/' file

l 开关将输出记录分隔符设置为输入记录分隔符(默认为换行符)。
0 开关将输入记录分隔符设置为 >(八进制为 76)。