Регулярное выражение s/\A\s*\n// удаляет каждую строку, состоящую из пробелов, из начала строки. Он оставляет все остальное в покое, включая любые пробелы, которые могут начинать первую видимую строку. Под "видимой линией" я подразумеваю линию, удовлетворяющую /\S/. Код ниже демонстрирует это.

Но как это работает?

\A закрепляет начало строки

\s* жадно захватывает все пробелы. Но без модификатора (?s) он должен останавливаться в конце первой строки, не так ли? Видеть https://perldoc.perl.org/perlre.

Предположим, что без модификатора (?s) он, тем не менее, «обрабатывает строку как одну строку». Тогда я ожидал бы, что жадный \s* захватит каждый пробельный символ, который он видит, включая перевод строки. Таким образом, он пропустит перевод строки, который предшествует строке «dog», продолжит захват пробелов, натолкнется на «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";
}

2
Jacob Wegelin 13 Окт 2021 в 20:32

2 ответа

Лучший ответ

Но как это работает?
...
Я ожидал, что жадный \ s * захватит каждый пробельный символ, который он видит, включая перевод строки. Таким образом, он пропустит перевод строки, который предшествует строке «dog», продолжит захват пробелов, натолкнется на «d», и мы никогда не получим совпадения.

Правильно - \s* сначала захватывает все до ddogs), и при этом совпадение не удастся ... так что он выполняет резервное копирование, по символу за раз, сокращение этого жадного захвата, чтобы дать возможность следующему шаблону, здесь \n, соответствовать.

И это работает! Итак, \s* соответствует (последнему!) \n, этому соответствует следующий \n в шаблоне, и все в порядке. Он удален, и мы остаемся с напечатанным "\tdogs".

Это называется обратным отслеживанием. См. Также в perlretut. Отслеживание с возвратом можно подавить, в первую очередь с помощью односторонних форм, или, скорее, с помощью расширенной конструкции (?>...).


Но без модификатора (? S) он должен останавливаться в конце первой строки, не так ли?

Здесь вы можете спутать \s с ., который действительно не соответствует \n (без /s)

2
zdim 13 Окт 2021 в 18:36

Здесь есть два вопроса.


Первый касается взаимодействия \s и (отсутствия) (?s). Проще говоря, взаимодействия нет.

\s соответствует символам пробелов, включая перевод строки (LF). На него никак не влияет (?s).

(?s) влияет исключительно на ..

  • (?-s) заставляет . соответствовать всем символам, кроме LF. [Дефолт]
  • (?s) заставляет . соответствовать всем символам.

Если кто-то хочет сопоставить пробелы в текущей строке, можно использовать \h вместо \s. Он соответствует только горизонтальным пробелам, исключая CR и LF (среди прочего).

В качестве альтернативы, (?[ \s - \n ]) [1] , [^\S\n] [2] и \s(?<!\n) [3] все соответствуют пробельным символам, отличным от LF.


Второй - неправильное представление о том, что такое жадность.

Жадность или ее отсутствие не влияет на соответствие шаблону, а только на соответствие . Например, для заданного ввода /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* избегает сопоставления LF и табуляции перед dog в вашем примере.


  1. В настоящее время требуется use experimental qw( regex_sets );, но я считаю это безопасным.
  2. Менее понятно, потому что здесь используются двойные отрицания.
    [^\S\n]
    = Знак, который (not (not (\ s) или LF))
    = Знак, который (not (not ( \ s)), а не (LF))
    = символ, который (\ s, а не LF)
  3. Менее эффективно и далеко не так красиво, как набор регулярных выражений.
2
ikegami 13 Окт 2021 в 19:18