有没有更好的方法用/ x编写Perl正则表达式,所以代码仍然易于阅读?

Bri*_*anH 8 regex perl perl-critic

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

Regular expression without "/x" flag at line 21, column 26. See page 236 of PBP.
Run Code Online (Sandbox Code Playgroud)

在这里查看了策略信息,并且我理解在扩展模式下编写正则表达式将有助于任何正在查看代码的人.

但是,我被困在如何转换我的代码以使用/ 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;
Run Code Online (Sandbox Code Playgroud)

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

我的代码:

if ($line =~ /^\s*package\s+(\S+);/ ) {
Run Code Online (Sandbox Code Playgroud)

我不确定如何在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
      )
    {
Run Code Online (Sandbox Code Playgroud)

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

我不是在寻找重写这个特定正则表达式的建议(尽管如果我可以改进它,我会接受建议) - 我更关注如何在if语句中扩展正则表达式的建议.

我知道Perl :: Critic只是一个指南,但跟随它会很好.

提前致谢!

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

Cha*_*ens 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$/;
Run Code Online (Sandbox Code Playgroud)


Sin*_*nür 11

好吧,我真的不认为你应该浪费垂直屏幕房地产.另一方面,如果我将这个模式写在几行上,我会使用大括号并缩进模式:

if ($line =~ m{
        \A \s*
        package
        \s+
        (\S+)
        \s* ;
    }x 
) {
Run Code Online (Sandbox Code Playgroud)

恕我直言,以下版本完全没问题:

if ( $line =~ m{ \A \s* package \s+ (\S+) \s* ; }x  ) {
Run Code Online (Sandbox Code Playgroud)

在获得利益方面m//x.

在这种情况下,评论是完全没有必要的,因为你没有做任何棘手的事情.我\s*在分号之前添加了因为有时人们将分号设置为与包名不同而且不应该丢掉你的匹配.

  • 我想我以前没想过在单行正则表达式中添加空格.我总是认为"/ x"标志只是一个多行标志,但我真的很喜欢上面的例子. (2认同)
  • @BrianH:不,不是真的.如果你使用/ m只会有所不同,当你使用/ m时,你通常想要^,而不是\ A. 另一方面,$经常用于人们真正意义上的\ z. (2认同)

Rob*_*lls 8

几乎是你对这些额外信息所增加的价值的呼吁.

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

实际上,关于附加信息的附加价值的"打电话"可能非常困难.

我记不清有多少次我看过遗留代码,其中没有维护精美格式的注释,因此偏离代码所做的事情.事实上,当我经验不足时,我完全走错了路,因为与一段代码相关的评论已经过时并且没有得到维护.

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

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

这告诉我更多

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

我的感觉是在这种情况下留下正则表达式的评论.你的直觉是对的!


Ken*_*ric 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";
}
Run Code Online (Sandbox Code Playgroud)

请注意添加的(?!\d)片段,以便添加

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