7

sed如果我需要提取由特定模式包围的模式,如果它存在于一行中,我可以使用它吗?

假设我有一个包含以下几行的文件:

有很多人因为[/fear/]邻居会说什么而不敢自杀。

/*当我们已经知道答案*/但希望我们不知道时,我们会寻求建议。

在这两种情况下,我必须在各自的情况下扫描第一个出现的模式,即'[ /'或' ',并存储以下模式,直到退出模式,即' ]'或' '分别。/*/*/

简而言之,我需要fearanswer。如果可能,是否可以将其扩展到多行;从某种意义上说,如果退出模式出现在与同一行不同的行中。

欢迎任何形式的建议或算法帮助。提前感谢您的回复

4

3 回答 3

4
use strict;
use warnings;

while (<DATA>) {
    while (m#/(\*?)(.*?)\1/#g) {
        print "$2\n";
    }
}


__DATA__
There are many who dare not kill themselves for [/fear/] of what the neighbors will say.
Advice is what we ask for when we already know the /* answer */ but wish we didn’t.

作为一个单行:

perl -nlwe 'while (m#/(\*?)(.*?)\1/#g) { print $2 }' input.txt

内部 while 循环将在所有带有/g修饰符的匹配项之间进行迭代。反向引用\1将确保我们只匹配相同的打开/关闭标签。

如果您需要匹配跨越多行的块,则需要 slurp 输入:

use strict;
use warnings;

$/ = undef;
while (<DATA>) {
    while (m#/(\*?)(.*?)\1/#sg) {
        print "$2\n";
    }
}

__DATA__
    There are many who dare not kill themselves for [/fear/] of what the neighbors will say. /* foofer */ 
    Advice is what we ask for when we already know the /* answer */ but wish we didn’t.
foo bar /
baz 
baaz / fooz

单线:

perl -0777 -nlwe 'while (m#/(\*?)(.*?)\1/#sg) { print $2 }' input.txt

-0777开关 和将$/ = undef导致文件 slurping,这意味着所有文件都被读入一个标量。我还添加了/s修饰符以允许通配符.匹配换行符。

正则表达式的解释:m#/(\*?)(.*?)\1/#sg

m#              # a simple m//, but with # as delimiter instead of slash
    /(\*?)      # slash followed by optional *
        (.*?)   # shortest possible string of wildcard characters
    \1/         # backref to optional *, followed by slash
#sg             # s modifier to make . match \n, and g modifier 

这里的“魔力”在于,*只有在它之前找到一个星号时,反向引用才需要一个星号。

于 2012-06-19T14:37:53.900 回答
1

快速而肮脏的方式awk

awk 'NF{ for (i=1;i<=NF;i++) if($i ~ /^\[\//) { print gensub (/^..(.*)..$/,"\\1","g",$i); } else if ($i ~ /^\/\*/) print $(i+1);next}1' input_file

测试:

$ cat file
There are many who dare not kill themselves for [/fear/] of what the neighbors will say.

Advice is what we ask for when we already know the /* answer */ but wish we didn't.
$ awk 'NF{ for (i=1;i<=NF;i++) if($i ~ /^\[\//) { print gensub (/^..(.*)..$/,"\\1","g",$i); } else if ($i ~ /^\/\*/) print $(i+1);next}1' file
fear

answer
于 2012-06-19T14:57:44.010 回答
1

单行匹配

如果你真的想在 sed 中这样做,你可以相对容易地提取你的分隔模式,只要它们在同一行。

# Using GNU sed. Escape a whole lot more if your sed doesn't handle
# the -r flag.
sed -rn 's![^*/]*(/\*?.*/).*!\1!p' /tmp/foo

多行匹配

如果你想用 sed 执行多行匹配,事情会变得有点难看。但是,它肯定可以做到。

# Multi-line matching of delimiters with GNU sed.
sed -rn ':loop
         /\/[^\/]/ { 
             N
             s![^*/]+(/\*?.*\*?/).*!\1!p
             T loop
         }' /tmp/foo

诀窍是寻找一个起始分隔符,然后在循环中不断追加行,直到找到结束分隔符。

只要您确实一个结束分隔符,这就会非常有效。否则,文件的内容将继续附加到模式空间,直到 sed 找到一个,或者直到它到达文件的末尾。这可能会导致某些版本的 sed 或模式空间大小失控的非常非常大的文件出现问题。

有关更多信息,请参阅GNU sed 的限制和非限制

于 2012-06-20T08:53:02.113 回答