微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

/m 修饰符的 perl 正则表达式意外行为

如何解决/m 修饰符的 perl 正则表达式意外行为

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

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

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

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

给出结果:

a
b

(出乎我意料的是,中间有空格的双 \n 变成了单 \n)

但请注意:

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

结果:

ab

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

解决方法

使用 -Mre=debug 模块并深入研究细节,我找到了我认为的答案。我删除了前导空格,因为它与问题无关。我删除了除相关部分之外的所有内容。两个正则表达式首先使用 RHS (5:BRANCH) 匹配第二个换行符前面的空格/换行符,然后将指针设置在第二个换行符前面:

情况 1:字符串 a \n \n b\n

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) 失败,所以它向前移动一步,直到换行符之后,LHS 匹配,并删除前面的它:一个空格。

在换行符和 b 前面的空格之间的匹配中,当正则表达式中的“指针”在换行符前面向前移动时。

%n> < b%n>
^   \s

情况 2:字符串 a \n\n b\n

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>
^   \s\s

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

如何避免这种行为?嗯,问题是你的正则表达式太松了。 \n 可以以各种组合匹配正则表达式 ^$\s 的所有组件。它还可以匹配字符串的中间。如果您想安全并获得可预测的结果,请在逐行模式下使用正则表达式,不要将文件变成单个字符串。那么你就不需要多行匹配了,所有的问题都会迎刃而解。

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

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

,

\s 可以匹配换行符,导致换行符被移除的问题。

\s 替换为以下之一:

  • [^\S\n]
    匹配既不是非空白字符也不是换行符的字符,即不是换行符的空白字符。
  • (?[ \s - \n ])
    目前处于实验阶段,需要 use experimental qw( regex_sets );
  • \h
    仅删除水平空白字符。虽然它不匹配换行符,但它也不匹配其他垂直空白字符。[1]

接下来详细说明您的模式是如何匹配的。


为了

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

模式

/^\s*|\s*$/m

产生以下匹配:

  1. 位置 0,长度 1:^\s* 匹配。
  2. 位置 2,长度 3:␠␊␠\s*$ 匹配。 XXX
  3. 位置 5,长度 0:\s*$ 匹配的空字符串
  4. 位置 6,长度 1:^\s* 匹配。
  5. 位置 8,长度 1:\s*$ 匹配。 XXX
  6. 位置 9,长度 0:与 ^\s* 匹配的空字符串。

为了

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

模式

/^\s*|\s*$/m

产生以下匹配:

  1. 位置 0,长度 1:^\s* 匹配。
  2. 位置 2,长度 2:␠␊\s*$ 匹配。 XXX
  3. 位置 4,长度 2:␊␠^\s* 匹配。 XXX
  4. 位置 7,长度 1:\s*$ 匹配。 XXX
  5. 位置 8,长度 0:与 ^\s* 匹配的空字符串。

脚注:

  1. 垂直空白:

    • U+000A 换行符
    • U+000B 线制表
    • U+000C 表单反馈
    • U+000D 回车
    • U+0085 下一行
    • U+2028 换行符
    • U+2029 段落分隔符

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。