perl 正则表达式从字符串中删除初始的全空白行:为什么它有效?

perl regex to remove initial all-whitespace lines from a string: why does it work?

正则表达式 s/\A\s*\n// 从字符串的开头删除每个全空白行。 它保留所有其他内容,包括可能开始第一条可见行的任何空白。 “可见线”是指满足 /\S/ 的线。 下面的代码演示了这一点。

但是它是如何工作的?

\A 锚定字符串的开头

\s* 贪婪地抓住所有空白。但是没有(?s)修饰符,它应该停在第一行的末尾,不是吗? 看 https://perldoc.perl.org/perlre.

假设没有 (?s) 修饰符它仍然“将字符串视为单行”。 然后我希望贪婪的 \s* 抓住它看到的每个空白字符, 包括换行符。所以它会传递“dogs”字符串之前的换行符,继续抓取空格,运行 到“d”,我们永远不会得到匹配项。

尽管如此,代码完全符合我的要求。因为我无法解释它,所以它就像一个拼凑,碰巧有效的东西,通过反复试验发现的。它起作用的原因是什么?

#!/usr/bin/env perl 
use strict; use warnings;
print $^V; print "\n";

my @strs=(
    join('',"\n", "\t", ' ', "\n", "\t", ' dogs',),
    join('',
              "\n",
              "\n\t\t\x20",
              "\n\t\t\x20",
    '......so what?',
              "\n\t\t\x20",
    ),
);

my $count=0;
for my $onestring(@strs)
{
    $count++;
    print "\n$count ------------------------------------------\n"; 
    print "|$onestring|\n";
    (my $try1=$onestring)=~s/\A\s*\n//;
    print "|$try1|\n";
}

这里有两个问题。


首先是关于 \s 和(缺少)(?s) 的交互。很简单,没有互动。

\s 匹配空白字符,包括换行 (LF)。它不受 (?s) 任何影响。

(?s) 只影响 ..

  • (?-s) 导致 . 匹配除 LF 之外的所有字符。 [默认]
  • (?s) 导致 . 匹配所有字符。

如果想匹配当前行的空格,可以使用 \h 而不是 \s。它只匹配水平空格,因此不包括 CR 和 LF(以及其他)。

或者,(?[ \s - \n ])[1][^\S\n][2]\s(?<!\n) [3] 全部匹配 LF 以外的空白字符。


第二个是对贪婪的误解。

是否贪婪不会影响 if 模式可以匹配,只会影响 what 它匹配。例如,对于给定的输入,/a+//a+?/ 都匹配,或者都不匹配。不可能一个匹配一个不匹配。

"aaaa" =~ /a+/    # Matches 4 characters at position 0.
"aaaa" =~ /a+?/   # Matches 1 character  at position 0.

"bbbb" =~ /a+/    # Doesn't match.
"bbbb" =~ /a+?/   # Doesn't match.

当一个东西是贪心的时候,意味着它会在当前位置匹配最可能的,让整个模式匹配。以下面的例子:

"ccccd" =~ /.*d/

这个模式可以通过让 .* 只匹配 cccc 而不是 ccccd 来匹配,因此这样做了。这是通过回溯实现的。 .* 最初匹配 ccccd,然后它发现 d 不匹配,所以 .* 只尝试匹配 cccc。这允许 d 以及整个模式匹配。

你会发现在贪婪之外也使用了回溯。 "efg" =~ /^(e|.f)g/ 匹配,因为它在使用第一个替代方案时无法匹配 g 时会尝试第二个替代方案。

.* 避免匹配前面示例中的 d 相同,\s* 避免匹配您示例中 dog 之前的 LF 和制表符。


  1. 目前需要 use experimental qw( regex_sets );,但我认为它是安全的。
  2. 不太清楚,因为它使用双重否定。
    [^\S\n]
    = 一个字符是 ( not( not(\s) or LF ) )
    =一个字符是 ( not(not(\s)) and not(LF) )
    = 一个字符是( \s 而不是 LF )
  3. 效率较低,远不如正则表达式集漂亮。

But how does it work?
...
I would expect the greedy \s* to grab every whitespace character it sees, including linefeeds. So it would pass the linefeed that precedes the "dogs" string, keep grabbing whitespace, run into the "d", and we would never get a match.

正确 - \s* 一开始会抓取 d 之前的所有内容(在 dogs 中),这样匹配就会失败......所以它备份了,a一次一个字符,缩短贪婪的抓取,以便有机会匹配以下模式,此处 \n

这很有效!所以 \s* 匹配到(最后一个!)\n,该匹配与模式中的以下 \n 匹配,一切都很好。这已被删除,我们保留打印的 "\tdogs"

这叫做backtracking. See about it also in perlretut. Backtracking can be suppressed, most notably by possesive forms (like \w++ etc), or rather by extended construct (?>...)


But without the (?s) modifier, it should stop at the end of the first line, should it not?

这里你可能会混淆\s.,这确实不匹配\n(没有/s