9

我在我的一个脚本上运行了 Perl::Critic,并收到了这条消息:

Regular expression without "/x" flag at line 21, column 26. See page 236 of PBP.

我在此处查找了策略信息,并且我了解在扩展模式下编写正则表达式将对正在查看代码的任何人有所帮助。

但是,我被困在如何将我的代码转换为使用 /x 标志。

CPAN 示例:

# Match a single-quoted string efficiently...

m{'[^\\']*(?:\\.[^\\']*)*'};  #Huh?

# Same thing with extended format...

m{
    '           # an opening single quote
    [^\\']      # any non-special chars (i.e. not backslash or single quote)
    (?:         # then all of...
        \\ .    #    any explicitly backslashed char
        [^\\']* #    followed by an non-special chars
    )*          # ...repeated zero or more times
    '           # a closing single quote
}x;

如果您只看正则表达式,这是有道理的。

我的代码:

if ($line =~ /^\s*package\s+(\S+);/ ) {

我不确定如何在 if 语句中使用扩展的正则表达式。我可以这样写:

    if (
        $line =~ /
        ^\s*    # starting with zero or more spaces
        package
        \s+     # at least one space
        (\S+)   # capture any non-space characters
        ;       # ending in a semi-colon
        /x
      )
    {

这行得通,但我认为这几乎比原版更难阅读。有没有更好的方法(或最佳实践方法)来写这个?我想我可以使用 qr// 创建一个变量。

我并不是真的在寻找关于重写这个特定的正则表达式的建议(尽管如果我能改进它,我会接受建议)——我更多的是在寻找关于如何在 if 语句中扩展正则表达式的建议。

我知道 Perl::Critic 只是一个指导方针,但最好遵循它。

提前致谢!

编辑: 所以在收到一些答案后,我很清楚,并不总是需要使用带有注释的正则表达式多行。了解基本正则表达式的人应该能够理解我的示例在做什么——我添加的评论可能有点不必要和冗长。我喜欢使用扩展正则表达式标志的想法,但仍然在正则表达式中嵌入空格以使正则表达式的每个部分更加清晰。感谢所有的投入!

4

5 回答 5

12

永远不要写注释来说明代码所说的内容。注释应该告诉你为什么代码说它所说的。看看这个怪物,没有评论很难看到发生了什么,但评论清楚地表明要匹配的内容:

require 5.010;
my $sep         = qr{ [/.-] }x;               #allowed separators    
my $any_century = qr/ 1[6-9] | [2-9][0-9] /x; #match the century 
my $any_decade  = qr/ [0-9]{2} /x;            #match any decade or 2 digit year
my $any_year    = qr/ $any_century? $any_decade /x; #match a 2 or 4 digit year

#match the 1st through 28th for any month of any year
my $start_of_month = qr/
    (?:                         #match
        0?[1-9] |               #Jan - Sep or
        1[0-2]                  #Oct - Dec
    )
    ($sep)                      #the separator
    (?: 
        0?[1-9] |               # 1st -  9th or
        1[0-9]  |               #10th - 19th or
        2[0-8]                  #20th - 28th
    )
    \g{-1}                      #and the separator again
/x;

#match 28th - 31st for any month but Feb for any year
my $end_of_month = qr/
    (?:
        (?: 0?[13578] | 1[02] ) #match Jan, Mar, May, Jul, Aug, Oct, Dec
        ($sep)                  #the separator
        31                      #the 31st
        \g{-1}                  #and the separator again
        |                       #or
        (?: 0?[13-9] | 1[0-2] ) #match all months but Feb
        ($sep)                  #the separator
        (?:29|30)               #the 29th or the 30th
        \g{-1}                  #and the separator again
    )
/x;

#match any non-leap year date and the first part of Feb in leap years
my $non_leap_year = qr/ (?: $start_of_month | $end_of_month ) $any_year/x;

#match 29th of Feb in leap years
#BUG: 00 is treated as a non leap year
#even though 2000, 2400, etc are leap years
my $feb_in_leap = qr/
    0?2                         #match Feb
    ($sep)                      #the separtor
    29                          #the 29th
    \g{-1}                      #the separator again
    (?:
        $any_century?           #any century
        (?:                     #and decades divisible by 4 but not 100
            0[48]       | 
            [2468][048] |
            [13579][26]
        )
        |
        (?:                     #or match centuries that are divisible by 4
            16          | 
            [2468][048] |
            [3579][26]
        )
        00                      
    )
/x;

my $any_date  = qr/$non_leap_year|$feb_in_leap/;
my $only_date = qr/^$any_date$/;
于 2009-06-12T16:51:52.220 回答
11

好吧,我真的不认为你应该在这上面浪费垂直屏幕空间。另一方面,如果我要将此模式写成多行,我会使用大括号并缩进该模式:

if ($line =~ m{
        \A \s*
        package
        \s+
        (\S+)
        \s* ;
    }x 
) {

恕我直言,以下版本非常好:

if ( $line =~ m{ \A \s* package \s+ (\S+) \s* ; }x  ) {

在获得利益方面m//x

在这种情况下,评论是完全没有必要的,因为你没有做任何棘手的事情。我确实在\s*分​​号之前添加了,因为有时人们将分号与包名分开,这不会影响你的匹配。

于 2009-06-12T16:00:53.200 回答
8

对于这些额外信息所增加的价值,这几乎是您的要求。

有时你是对的,它没有添加任何东西来解释正在发生的事情,只是让代码看起来很混乱,但对于复杂的正则表达式,x标志可能是一个福音。

实际上,这种关于附加信息的附加价值的“打电话”可能相当困难。

我不记得有多少次我看到遗留代码没有维护格式精美的注释,因此偏离了代码正在做的事情。事实上,当我经验不足的时候,我完全走错了路,因为与一段代码相关的注释很旧并且没有得到维护。

编辑:在某些方面,CPAN 示例并没有那么有用。当使用 x 标志添加注释来描述复杂的正则表达式时,我倾向于描述正则表达式试图匹配的组件,而不仅仅是描述正则表达式“位”本身。例如,我会写如下内容:

  • 英国邮政编码的第一个组成部分(地区和地区),或
  • 英国的国际区号,或
  • 任何英国手机号码。

这告诉我的不仅仅是

  • 一个或两个字母,后跟一个数字,可选地后跟一个字母,或
  • 两个四位数在一起,或
  • 一个零,后跟四个十进制数字,一个破折号,然后是六个十进制数字。

我的感觉是在这种情况下留下正则表达式注释。你的直觉是对的!

于 2009-06-12T16:02:34.820 回答
6

看到这个话题是关于写正则表达式的替代方法,有写方法不带变量和不带注释的复杂正则表达式,它仍然很有用。

我将 Chas Owens 日期验证正则表达式重排为 Perl-5.10 中可用的新声明形式,它有很多好处。

  • 正则表达式中的标记是可重用的
  • 稍后打印正则表达式的任何人仍将看到整个逻辑树。

它可能不是每个人的鱼壶,但对于日期验证等极其复杂的事情,它可以很方便(ps:在现实世界中,请使用模块来处理日期的东西,不要DIY,这只是一个学习的例子从 )

#!/usr/bin/perl 
use strict;
use warnings;
require 5.010;

#match the 1st through 28th for any month of any year
my $date_syntax = qr{
    (?(DEFINE)
        (?<century>
            ( 1[6-9] | [2-9][0-9] )
        )
        (?<decade>
            [0-9]{2} (?!\d)
        )
        (?<year>
            (?&century)? (?&decade)(?!\d)
        )
        (?<leapdecade> (
            0[48]       | 
            [2468][048] |
            [13579][26]
            )(?!\d)
        )
        (?<leapcentury> (
            16          | 
            [2468][048] |
            [3579][26]
            )
        )   
        (?<leapyear>
            (?&century)?(?&leapdecade)(?!\d)
            |
            (?&leapcentury)00(?!\d)
        )
        (?<monthnumber>      ( 0?[1-9] | 1[0-2] )(?!\d)                  )
        (?<shortmonthnumber> ( 0?[469] | 11     )(?!\d)                  )
        (?<longmonthnumber>  ( 0?[13578] | 1[02] )(?!\d)                 )
        (?<nonfebmonth>      ( 0?[13-9] | 1[0-2] )(?!\d)                 )
        (?<febmonth>         ( 0?2 )(?!\d)                               )
        (?<twentyeightdays>  ( 0?[1-9] | 1[0-9] | 2[0-8] )(?!\d)         )
        (?<twentyninedays>   ( (?&twentyeightdays) | 29 )(?!\d)          )
        (?<thirtydays>       ( (?&twentyeightdays) | 29 | 30 )(?!\d)     )
        (?<thirtyonedays>    ( (?&twentyeightdays) | 29 | 30 | 31 )(?!\d))
        (?<separator>        [/.-]                              )               #/ markdown syntax highlighter fix
        (?<ymd>
            (?&leapyear) (?&separator) (?&febmonth) (?&separator) (?&twentyninedays) (?!\d)
            |
            (?&year) (?&separator) (?&longmonthnumber) (?&separator) (?&thirtyonedays) (?!\d)
            |
            (?&year) (?&separator) (?&shortmonthnumber) (?&separator) (?&thirtydays) (?!\d)
            |
            (?&year) (?&separator) (?&febmonth) (?&separator) (?&twentyeightdays) (?!\d)
        )
        (?<mdy>
            (?&febmonth) (?&separator) (?&twentyninedays) (?&separator) (?&leapyear)  (?!\d)
            |
            (?&longmonthnumber) (?&separator) (?&thirtyonedays) (?&separator) (?&year) (?!\d)
            |
            (?&shortmonthnumber) (?&separator) (?&thirtydays) (?&separator) (?&year) (?!\d)
            |
            (?&febmonth) (?&separator) (?&twentyeightdays) (?&separator) (?&year) (?!\d)
        )
        (?<dmy>
            (?&twentyninedays) (?&separator) (?&febmonth) (?&separator) (?&leapyear)  (?!\d)
            |
            (?&thirtyonedays) (?&separator) (?&longmonthnumber) (?&separator)(?&year) (?!\d)
            |
            (?&thirtydays) (?&separator) (?&shortmonthnumber) (?&separator) (?&year) (?!\d)
            |
            (?&twentyeightdays) (?&separator) (?&febmonth) (?&separator)  (?&year) (?!\d)
        )
        (?<date>
            (?&ymd) | (?&mdy) | (?&dmy)
        )
        (?<exact_date>
           ^(?&date)$
       )
    )
}x;

my @test = ( "2009-02-29", "2009-02-28", "2004-02-28", "2004-02-29", "2005-03-31", "2005-04-31", "2005-05-31", 
    "28-02-2009","02-28-2009",        
);

for (@test) {
  if ( $_ =~ m/(?&exact_date) $date_syntax/x ) {
    print "$_ is valid\n";
  }
  else {
    print "$_ is not valid\n";
  }

  if ( $_ =~ m/^(?&ymd) $date_syntax/x ) {
    print "$_ is valid ymd\n";
  }
  else {
    print "$_ is not valid ymd\n";
  }


  if ( $_ =~ m/^(?&leapyear) $date_syntax/x ) {
    print "$_ is leap (start)\n";
  }
  else {
    print "$_ is not leap (start)\n";
  }

  print "\n";
}

请注意添加的(?!\d)片段,这些片段是这样添加的

~= m{(?&twentyeightdays) $syntax}由于 '4' 匹配 0,“45”将不匹配?[4]

于 2009-06-12T20:23:10.857 回答
1

似乎这更像是一个问题,即如果条件……有很多答案,如何一致地缩进多行。真正重要的是一致性。如果您使用 perltidy 或其他格式化程序,请与它提供的内容(与您的配置)保持一致。不过,我会将正则表达式的内容从分隔符中缩进一级。

您的帖子显示了通过 Perl::Critic 之类的东西运行现有代码的一个主要缺陷 -的 CPAN 示例从原始正则表达式中遗漏了一个 *。如果你做了很多“清理”,你可能会引入错误,所以我希望你有一个好的测试套件。

于 2009-06-12T16:03:13.663 回答