【问题标题】:How can I match strings that don't match a particular pattern in Perl?如何匹配与 Perl 中特定模式不匹配的字符串?
【发布时间】:2010-01-22 22:39:01
【问题描述】:

我知道使用正则表达式很容易匹配给定字符以外的任何内容。

$text = "ab ac ad";
$text =~ s/[^c]*//g; # Match anything, except c.

$text is now "c".

我不知道如何“排除”字符串而不是字符。我将如何“匹配任何东西,除了'ac'”?试过 [^(ac)] 和 [^"ac"] 没有成功。

有可能吗?

【问题讨论】:

  • 对于简单的“字符串(不)包含在其他字符串中”,最好使用索引(如果不包含则返回 -1)。我写了一个邮件服务器日志文件解析器,它首先使用正则表达式进行那些简单的检查,在我切换到 index() 之后,与正则表达式版本相比,它的运行时间减少了大约 25%。作为奖励 index() 可能更容易阅读。
  • 您的最终目标到底是什么? (1) 匹配不包含ac(或其他一些子字符串)的字符串,或 (2) 替换字符串中除子字符串ac 之外的所有内容(例如将abacadac 更改为acac )?我怀疑是后者。
  • 要添加到@Bart 的评论:请提供以下示例:(1)您要验证和无效的字符串或(2)您要从该字符串中提取的字符串或(3)替换的结果在这个字符串中。
  • 我试图简化示例,但我的真正目标是清理一些 wiki 标记。基本上删除所有不包含“}}”的{{...}}。
  • 一个简单的例子很好,但请确保不要过于简单化——你的例子应该仍然能代表你想要解决的问题。

标签: regex perl


【解决方案1】:

以下解决了 Bart K. 评论中描述的第二种意义上的问题:

>> $text='ab ac ad';
>> $text =~ s/(ac)|./\1/g;
>> print $text;
ac

另外,'abacadac' -> 'acac'

应该注意的是,在大多数实际应用中,负前瞻被证明比这种方法更有用。

【讨论】:

  • 创意! :)(最少 15 个字符)
  • 打败我 :-)。一个轻微的改进可能是做 s/(\bac\b)/./\1/g 这意味着 'ac fac ac' -> acac
  • 我想到了不太有创意的模式:a([^c]|$)|(^|[^a])c|[^ac]
【解决方案2】:

如果你只想检查字符串是否不包含“ac”,只需使用否定即可。

$text = "ab ac ad";

print "ac not found" if $text !~ /ac/;

print "ac not found" unless $text =~ /ac/;

【讨论】:

    【解决方案3】:
    $text =~ s/[^c]*//g; // Match anything, except c.
    

    @ssn, 关于您的问题的几个cmets:

    1. "//" 在 Perl 中不是注释。仅有的 “#”是。
    2. "[^c]*" - 不需要 那里的“*”。 “[^c]”表示 由所有组成的字符类 除字母“c”外的字符。 然后你使用 /g 修饰符, 意味着文本中的所有此类事件都将是 替换(在你的例子中,用 没有什么)。 “零个或多个”(“*”) 因此修饰符是多余的。

    我如何“匹配任何东西,除了 'ac'" ?试过 [^(ac)] 和 [^"ac"] 没有成功。

    请阅读有关字符类的文档(请参阅命令行上的“perldoc perlre”,或在线http://perldoc.perl.org/perlre.html) - 您会看到它指出对于方括号内的字符列表,RE 将“匹配列表中的任何字符”。含义顺序无关紧要,没有“字符串”,只有字符列表。 “()”和双引号在方括号内也没有特殊含义。

    现在我不完全确定您为什么要谈论匹配,然后再举一个替换的例子。但是要查看字符串是否与子字符串“ac”不匹配,您只需取消匹配即可:

    use strict; use warnings;
    my $text = "ab ac ad";
    if ($text !~ m/ac/) {
       print "Yey the text doesn't match 'ac'!\n"; # this shouldn't be printed
    }
    

    假设您有一个文本字符串,其中嵌入了多次出现的子字符串。如果您只想要子字符串周围的文本,只需删除所有出现的子字符串:

    $text =~ s/ac//g;
    

    如果您想要相反 - 删除所有文本,除了所有出现的子字符串,我建议如下:

    use strict; use warnings;
    my $text = "ab ac ad ac ae";
    my $sub_str = "ac";
    my @captured = $text =~ m/($sub_str)/g;
    my $num = scalar @captured;
    print (($sub_str x $num) . "\n");
    

    这基本上计算子字符串出现在文本中的次数,并使用“x”运算符打印子字符串该次数。不是很优雅,我相信 Perl 大师可以想出更好的东西。


    @ennukiller

    my $text = "ab ac ad";
    $text !~ s/(ac)//g; # Match anything, except ac.
    

    这是不正确的,因为它会在“使用警告”下生成警告(“在无效上下文中无用使用负模式绑定 (!~)”),并且除了从文本中删除所有子字符串“ac”之外什么都不做,这可以更简单地写成我上面写的:

    $text =~ s/ac//g;
    

    【讨论】:

      【解决方案4】:

      更新:在对您的问题的评论中,您提到要清理 wiki 标记并删除 {{ ... }} 的平衡序列。 Perl FAQ 的第 6 节涵盖了这一点:Can I use Perl regular expressions to match balanced text?

      考虑以下程序:

      #! /usr/bin/perl
      
      use warnings;
      use strict;
      
      use Text::Balanced qw/ extract_tagged /;
      
      # for demo only
      *ARGV = *DATA;
      
      while (<>) {
        if (s/^(.+?)(?=\{\{)//) {
          print $1;
          my(undef,$after) = extract_tagged $_, "{{" => "}}";
      
          if (defined $after) {
            $_ = $after;
            redo;
          }
        }
      
        print;
      }
      
      __DATA__
      Lorem ipsum dolor sit amet, consectetur
      adipiscing elit. {{delete me}} Sed quis
      nulla ut dolor {{me too}} fringilla
      mollis {{ quis {{ ac }} erat.
      

      它的输出:

      Lorem ipsum dolor sit amet, consectetur
      肥胖精英。 Sed quis
      nulla ut dolor fringilla
      莫利斯 {{ quis erat.

      对于您的特定示例,您可以使用

      $text =~ s/[^ac]|a(?!c)|(?<!a)c//g;
      

      也就是说,仅当 ac 不属于 ac 序列时才删除它们。

      一般来说,这对于正则表达式来说是很棘手的。

      假设您不希望 foo 后跟可选空格,然后在 $str 中添加 bar。通常,单独检查会更清晰、更容易。例如:

      die "invalid string ($str)"
        if $str =~ /^.*foo\s*bar/;
      

      你可能也对我写的an answer to a similar question感兴趣

      my $nofoo = qr/
        (      [^f] |
          f  (?! o) |
          fo (?! o  \s* bar)
        )*
      /x;
      
      my $pattern = qr/^ $nofoo bar /x;
      

      要了解复杂性,请阅读 Mark Dominus 的 How Regexes Work。该引擎将正则表达式编译成状态机。当需要匹配时,它将输入字符串提供给状态机并检查状态机是否在接受状态下完成。所以要排除一个字符串,你必须指定一台机器接受除特定序列之外的所有输入。

      可能有帮助的是/v 正则表达式开关,它照常创建状态机,然后补充所有状态的接受状态位。很难说这与单独检查相比是否真的有用,因为 /v 正则表达式可能仍然会让人们感到惊讶,只是方式不同。

      如果您对理论细节感兴趣,请参阅 Peter Linz 的 An Introduction to Formal Languages and Automata

      【讨论】:

        【解决方案5】:

        你可以使用 index()

        $text = "ab ac ad";
        print "ac not found" if ( index($text,"ac") == -1 );
        

        【讨论】:

          【解决方案6】:

          您可以根据自己的目的轻松修改此正则表达式。

          use Test::More 0.88;
          
          #Match any whole text that does not contain a string
          my $re=qr/^(?:(?!ac).)*$/;
          my $str='ab ac ad';
          
          ok(!$str=~$re);
          
          $str='ab af ad';
          ok($str=~$re);
          
          done_testing();
          

          【讨论】:

          • Mark Byers:只要简单的修改,它就可以作用于字符串的任何部分,这只是一个例子。
          • 改变了我的想法 - 这将不起作用,原因与此处发布的其他解决方案不起作用的原因相同:它将删除 c.尝试在完整测试中进行“简单修改”,看看会发生什么。
          • 问题是:“匹配任何东西,除了 'ac'”。在我的正则表达式中删除 ^$ 后,它将匹配任何内容,在“ac”处停止。
          • 好的,抱歉。然后我完全误解了这个问题,对不起。我以为他想替换整个字符串,而不仅仅是开头。我的错。
          猜你喜欢
          • 1970-01-01
          • 2013-06-09
          • 1970-01-01
          • 2016-10-23
          • 2012-08-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多