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

标签 regex perl modifier

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

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) 匹配第二个换行符前面的空格/换行符,然后在第二个换行符前面设置指针:

情况一:字符串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) 失败,因此它向前移动 1 步,直到换行符之后,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

与其像Case 1那样跳过第一次尝试向前移动1步,不如直接匹配左边的换行符,右边的空格\n :

%n> <%n b%n>
^   \s\s

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

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

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

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

关于regex -/m 修饰符的 perl 正则表达式意外行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68141141/

相关文章:

描述二进制数的正则表达式

c# - 需要正则表达式来关闭或替换引号

perl - 在perl中的多个子例程调用中保留变量的值

perl - Perl 中的词法范围和动态范围有什么区别?

java - 需要帮助创建一个程序来找到最轻和最重的狗

java - “public” 或 “protected” 方法对于不实现任何接口(interface)的私有(private)嵌套类没有任何区别..?

Angular 无法编译 Typescript 的映射类型修饰符

javascript - 如何在 JS 正则表达式中将模式与非捕获组匹配

regex - 用正则表达式交换字母

java .matches() 不匹配