perl-regex/m修饰符的意外行为



我想用以下正则表达式从多行字符串中删除前导空格和尾随空格:

s/^s*|s*$//mg

在这个例子中,它似乎或多或少地工作得很好:

perl -e '$_=" a n n bn"; s/^s*|s*$//mg; print "$_n";'

这给出了结果:

a
b

(我没想到的是,中间有空格的双色已经变成了单色)

但请注意:

perl -e '$_=" a nn bn"; s/^s*|s*$//mg; print "$_n";'

结果:

ab

现在这两个都消失了,多行字符串现在是单行,这不是我想要的。如果这不是bug,我该如何避免这种行为?

使用-Mre=debug模块并深入了解细节,我找到了我认为的答案。我去掉了前导空格,因为它与问题无关。除了相关部件,我什么都拆了。两个正则表达式首先使用RHS(5:BRANCH)匹配第二个换行符前面的空格/换行符,然后在第二个新行前面设置指针:

案例1:字符串a n n bn

Matching REx "^s+|s+$" against "%n b%n"
4 <a %n > <%n b%n>        |   0| 1:BRANCH(5)
4 <a %n > <%n b%n>        |   1|  2:MBOL(3)
|   1|  failed...
4 <a %n > <%n b%n>        |   0| 5:BRANCH(9)
4 <a %n > <%n b%n>        |   1|  6:PLUS(8)
|   1|  POSIXD[s] can match 2 times out of 2147483647...
6 <a %n %n > <b%n>        |   2|   8:MEOL(9)
|   2|   failed...
5 <a %n %n> < b%n>        |   2|   8:MEOL(9)
|   2|   failed...
|   1|  failed...
|   0| BRANCH failed...
5 <a %n %n> < b%n>        |   0| 1:BRANCH(5)  <-- HERE!
5 <a %n %n> < b%n>        |   1|  2:MBOL(3)
5 <a %n %n> < b%n>        |   1|  3:PLUS(9)
|   1|  POSIXD[s] can match 1 times out of 2147483647...
6 <a %n %n > <b%n>        |   2|   9:END(0)
Match successful!

在这种情况下,LHS(1:BRANCH)首先失败,RHS(5:BRANCH。

在换行符和b前面的空格之间的匹配中;指针";在正则表达式中,已向前移动到换行符的前面。

%n> < b%n>
^   s

情况2:字符串a nn bn

Matching REx "^s+|s+$" against "%n b%n"
3 <a %n> <%n b%n>         |   0| 1:BRANCH(5) <-- HERE!
3 <a %n> <%n b%n>         |   1|  2:MBOL(3)
3 <a %n> <%n b%n>         |   1|  3:PLUS(9)
|   1|  POSIXD[s] can match 2 times out of 2147483647...
5 <a %n%n > <b%n>         |   2|   9:END(0)
Match successful!

在这个字符串中,LHS(1:BRANCH)中的零宽度断言^可以看到字符串左侧的换行符,并允许其匹配。在另一个字符串中,它有一个空格,因此无法匹配。因此LHS交流发电机匹配(称为1:BRANCH),并删除其前面的内容,即换行符和空格n

它可以直接在左边的换行符和右边的空白n上进行匹配,而不是像案例1那样跳过第一次尝试并向前移动1步:

%n> <%n b%n>
^   ss

TL;DR:在第二个字符串中,换行符可以匹配两个换行符之间的行首,因此可以同时删除它们。在第一个字符串中,它不能像那样匹配,因为那里有一个空格,而是向前移动一步,跳过换行符并使用该换行符来匹配字符串的开头。其效果是换行符保留在字符串中。

你怎样才能避免这种行为?问题是正则表达式太松散了。n可以以各种组合匹配正则表达式^$s的所有组件。它也可以在字符串的中间进行匹配。如果您希望安全并获得可预测的结果,请以逐行模式使用regex,不要将文件拖入单个字符串。这样你就不需要多行匹配了,所有的问题都会迎刃而解。

否则,请避免使用多行修饰符,只需像往常一样删除前导和尾部空白,然后在字符串内部修剪多个带空格的换行符,类似于s/ns*n/n/g

从本质上讲,你试图同时做太多的事情。使正则表达式更加严格,并尝试一次只做一件事。

s可以匹配换行符,这导致了删除换行符的问题。

s替换为以下内容之一:

  • h
    仅删除水平空白字符。虽然它不匹配换行符,但也不匹配其他垂直空白字符[1]
  • (?[ s - n ])
    这需要use experimental qw( regex_sets );在5.36之前。但添加此功能并使用该功能是安全的,早在5.18中将其作为实验功能引入时,因为从那时起该功能就没有任何更改
  • [^Sn]
    匹配一个既不是非空白字符也不是换行符的字符,也就是说一个非换行符的空白字符

下面详细介绍了您的模式是如何匹配的。


对于

␠ a ␠ ␊ ␠ ␊ ␠ b ␊
0 1 2 3 4 5 6 7 8 9

图案

/^s*|s*$/m

生成以下匹配项:

  1. Pos 0,len 1:^s*匹配
  2. Pos 2,len 3:␠␊␠s*$匹配。XXX
  3. Pos 5,len 0:s*$匹配的空字符串
  4. Pos 6,len 1:^s*匹配
  5. Pos 8,len 1:s*$匹配。XXX
  6. Pos 9,len 0:^s*匹配的空字符串

对于

␠ a ␠ ␊ ␊ ␠ b ␊
0 1 2 3 4 5 6 7 8

图案

/^s*|s*$/m

生成以下匹配项:

  1. Pos 0,len 1:^s*匹配
  2. Pos 2,len 2:␠␊s*$匹配。XXX
  3. Pos 4,len 2:␊␠^s*匹配。XXX
  4. Pos 7,len 1:s*$匹配。XXX
  5. Pos 8,len 0:^s*匹配的空字符串

脚注:

  1. 垂直空白:

    • U+000A管线进料
    • U+000B行制表
    • U+000C表单馈送
    • U+000D回车
    • U+0085下一行
    • U+2028线路分离器
    • U+2029段落分隔符

最新更新