【问题标题】:One-liner to print all lines between two patterns单行打印两个图案之间的所有线条
【发布时间】:2019-04-18 01:31:16
【问题描述】:

使用一行 Perl 代码,打印两个模式之间的所有行(不包括带有模式的行)的最短方法是什么?

如果这是file.txt:

aaa
START
bbb
ccc
ddd
END
eee
fff

我想打印这个:

bbb
ccc
ddd

我可以使用这样的方式到达那里的大部分路:

perl -ne 'print if (/^START/../^END/);'

不过,这包括 STARTEND 行。

我可以这样完成工作:

perl -ne 'if (/^START/../^END/) { print unless (/^(START)|(END)/); };' file.txt

但这似乎是多余的。

我真正想做的是使用像这样的lookbehind和lookahead断言:

perl -ne 'print if (/^(?<=START)/../(?=END)/);' file.txt

但这不起作用,我认为我的正则表达式有点错误。

这些只是我尝试过的一些不产生输出的变体:

perl -ne 'print if (/^(?<=START)/../^.*$(?=END)/);' file.txt
perl -ne 'print if (/^(?<=START)/../^.*(?=END)/);' file.txt
perl -ne 'print if (/^(?<=START)/../(?=END)/);' file.txt
perl -ne 'print if (/^(?<=START)/../.*(?=END)/);' file.txt
perl -ne 'print if (/^(?<=START)/../^.*(?=END)/);' file.txt
perl -ne 'print if (/^(?<=START)/../$(?=END)/);' file.txt
perl -ne 'print if (/^(?<=START)/../^(?=END)/);' file.txt
perl -ne 'print if (/^(?<=START)/../(?=^END)/);' file.txt
perl -ne 'print if (/^(?<=START)/../.*(?=END)/s);' file.txt

【问题讨论】:

  • 查看perlop中关于序列号的部分:perldoc.perl.org/perlop.html#Range-Operators
  • @ThisSuitIsBlackNot 谢谢。我看到“您可以通过等待序列号大于1来排除起点。”这允许我使用perl -ne 'print if ((/^START/../^END/) &gt; 1);' file.txt 跳过打印第一个模式,但它不是固定的行数,所以我不能排除范围内的最后一个模式。
  • "范围内的最终序列号附加了字符串E0,这不会影响其数值,但如果您想排除端点,则可以搜索一些内容。 "
  • @ThisSuitIsBlackNot 哈!谢谢 :) 我在您链接的页面上看到了它,直到现在才注册它的含义。在尝试对其进行测试时,我使用的是printf("%d\n"),所以我没有看到E0。现在我有perl -ne '$s = /^START/../^END/; print if ($s &gt; 1 &amp;&amp; $s !~ /E0/);' file.txt。它比我的 unless 版本更有效且更短,但我真的希望它能够与 lookarounds 一起使用。
  • 使用环视,您需要阅读整个文本并使用/(?&lt;=^START\n)(?:(?!^END$).)*/sm 之类的内容。缓和的贪婪令牌与未锚定的后视相结合实际上是一种过度杀伤,如果输入很大,这是一种非常低效的方法。

标签: regex perl regex-lookarounds


【解决方案1】:

读取整个文件,匹配并打印。

perl -0777 -e 'print <> =~ /START.*?\n(.*?)END.*?/gs;' file.txt

如果单独在线,可能会在START|END 之后删除.*?。 然后删除 \n 以在段之间添加一个空行。


读取文件,用START|END分割行,打印@F的每一个奇数

perl -0777 -F"START|END" -ane 'print @F[ grep { $_ & 1 } (0..$#F) ]' file.txt

使用END { } 块进行额外处理。将}{ 用于END { }

perl -ne 'push @r, $_ if (/^START/../^END/); }{ print "@r[1..$#r-1]"' file.txt

因为它仅代表文件中的单个此类段而工作。

【讨论】:

  • 谢谢!您在第一个示例中给了我最短的版本。我的第一个和第二个模式是单独在线的,所以我最终得到了perl -0 -e 'print &lt;&gt; =~ /START\n(.*?)END/gs;' file.txt。我不知道 -0 更改记录分隔符或菱形运算符(很难找到它的名称)。
  • @Vince 很高兴它有帮助:)。它的另一个有用的版本是-00,用于段落模式perlrun 你的最后一行也是我的第一句话..
【解决方案2】:

对此设置单行限制似乎有点武断,但这里有一种方法:

$ perl -wne 'last if /^END/; print if $p; $p = 1 if /^START/;' file.txt

【讨论】:

  • 这可以完成工作,但我希望有一种方法可以使其与前瞻/后瞻断言一起工作,或者解释为什么这个想法不起作用.如果几天后没有人提供使用 lookarounds 的解决方案,我会接受你的回答。
【解决方案3】:
perl -e 'print split(/.*START.|END.*/s, join("", <>))' file.txt

perl -ne 'print if /START/../END/' file.txt | perl -ne 'print unless $.==1 or eof'

perl -ne 'print if /START/../END/' file.txt | sed -e '$d' -n -e '1\!p'

【讨论】:

    【解决方案4】:

    我不明白您为什么如此坚持使用环视,但这里有几种方法。

    perl -ne 'print if /^(?=START)/../^(?=END)/'
    

    这会找到终止符而不实际匹配它们。满足前瞻的零长度匹配被匹配。

    你的lookbehind没有工作,因为它试图在同一行上找到^START之前的行开头,这显然永远不会匹配。将^ 分解为零宽度断言,它将起作用:

    perl -ne 'print if /(?<=^START)/../(?<=^END)/'
    

    正如 @ThisSuitIsBlackNot 在 cmets 中所建议的,您可以使用序列号来省略 STARTEND 标记。

    perl -ne '$s = /^START/../^END/; print if ($s>1 && $s !~ /E0/)'
    

    lookarounds 没有提供任何有用的东西,所以我没有完全开发这些示例。如果您更关心使用环视而不是代码可维护性和执行速度,则可以将其调整为上述环视示例之一。

    【讨论】:

    • 谢谢!起初,使用环视似乎是最干净、最容易理解的解决方案。我正在使用的文件很小,它不是作为面向用户的过程的一部分执行的。我只是在节省一些时间从一个文件复制和粘贴到另一个文件。因此,就我而言,性能不是问题。如果像/(?=START)/../^(?=END)/ 这样的东西可以在不检查序列号的情况下工作,那么它就会很容易阅读和维护。当然,这只是我的看法。
    • 所以您认为外观可以在不打印的情况下帮助匹配?当然,在这种情况下,您正在检查一条孤立的线,并且无论该线的哪一部分匹配,总是会打印整条线。啜食整个文件和替换是不同的;当您替换匹配的部分时,正则表达式是否捕获分隔符并不重要。
    猜你喜欢
    • 1970-01-01
    • 2016-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多