Проблема с многострочным сопоставлением Perl - PullRequest
21 голосов
/ 03 мая 2011

Я пытаюсь использовать perl one-liner для обновления некоторого кода, который занимает несколько строк, и вижу странное поведение.Вот простой текстовый файл, который показывает проблему, с которой я сталкиваюсь:

ABCD    START
         STOP    EFGH

Я ожидал, что сработает следующее, но он ничего не заменит:

perl -pi -e 's/START\s+STOP/REPLACE/s' input.txt

После выполненияВ некоторых экспериментах я обнаружил, что \s+ в исходном регулярном выражении будет соответствовать символу новой строки, но не пробелу во 2-й строке, и добавление второго \s+ также не работает.Итак, сейчас я делаю следующий обходной путь, который заключается в добавлении промежуточного регулярного выражения, которое только удаляет символ новой строки:

perl -pi -e 's/START\s+/START/s' input.txt

Это создает следующий промежуточный файл:

ABCD    START            STOP    EFGH

ТогдаЯ могу запустить исходное регулярное выражение (хотя /s больше не требуется):

perl -pi -e 's/START\s+STOP/REPLACE/s' input.txt

Это создает окончательный, желаемый файл:

ABCD    REPLACE    EFGH

Кажется, что промежуточный шагне должно быть необходимо.Я что-то упустил?

Ответы [ 5 ]

23 голосов
/ 03 мая 2011

perl -p обрабатывает файл по одной строке за раз. У вас есть правильное регулярное выражение, но оно никогда не сопоставляется с многострочной строкой.

Простая стратегия, предполагающая, что файл помещается в память, состоит в том, чтобы прочитать все целиком (сделайте это без -p):

$/ = undef;
$file = <>;
$file =~ s/START\s+STOP/REPLACE/sg;
print $file;

Обратите внимание, я добавил модификатор /g, чтобы указать глобальную замену.

В качестве ярлыка для всех этих дополнительных шаблонов вы можете использовать существующий скрипт с опцией -0777: perl -0777pi -e 's/START\s+STOP/REPLACE/sg'. Добавление /g все еще необходимо, если вам может потребоваться сделать несколько замен в файле.

Икота, с которой вы можете столкнуться, но не с этим регулярным выражением: если регулярное выражение было START.+STOP, а файл содержит несколько пар START / STOP, жадное сопоставление .+ сожрет все, начиная с первого START и до Последняя остановка. Вы можете использовать не жадное сопоставление (сопоставлять как можно меньше) с .+?.

Если вы хотите использовать якоря ^ и $ для границ строк в любом месте строки, вам также понадобится модификатор /m regex.

21 голосов
/ 03 мая 2011

Вы были близки.Вам нужно либо -00, либо -0777:

 perl -0777 -pi -e 's/START\s+/START/' input.txt
5 голосов
/ 03 мая 2011

Относительно простая однострочная (чтение файла в памяти):

perl -pi -e 'BEGIN{undef $/;} s/START\s+STOP/REPLACE/sg;' input.txt

Еще одна альтернатива (не такая простая), не чтение файла в памяти:

perl -ni -e '$a.=$_; \
             if ( $a =~ s/START\s+STOP/REPLACE/s ) { print $a; $a=""; } \
             END{$a && print $a}' input.txt
3 голосов
/ 03 мая 2011

Вот одна строка, которая не читает весь файл в память сразу:

perl -i -ne 'if (($x = $last . $_) =~ s/START\n\s*STOP/REPLACE/) \
  { print $x; $last = ""; } else { print $last; $last = $_; } \
  print $last if eof ARGV' input.txt
2 голосов
/ 03 мая 2011
perl -MFile::Slurp -e '$content = read_file(shift); $content =~ s/START\s+STOP/REPLACE/s; print $content' input.txt
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...