Проблема с perl multiline matching
Я пытаюсь использовать perl one-liner для обновления кода, который охватывает несколько строк, и я вижу странное поведение. Вот простой текстовый файл, который показывает проблему, которую я вижу:
ABCD START
STOP EFGH
Я ожидал, что следующее будет работать, но это ничего не заменит:
perl -pi -e 's/START\s+STOP/REPLACE/s' input.txt
После некоторых экспериментов я обнаружил, что \s+
в исходном регулярном выражении будет соответствовать новой строке, но не пробелу во второй строке, а добавление второй \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
Кажется, что промежуточный шаг не нужен. Я что-то пропустил?
Ответы
Ответ 1
perl -p
обрабатывает файл по одной строке за раз. Регулярное выражение у вас правильно, но оно не сопоставляется с многострочной строкой.
Простая стратегия, предполагающая, что файл поместится в памяти, - это прочитать все (сделайте это без -p
):
$/ = undef;
$file = <>;
$file =~ s/START\s+STOP/REPLACE/sg;
print $file;
Примечание. Я добавил модификатор /g
, чтобы указать глобальную замену.
Как ярлык для всего этого дополнительного шаблона, вы можете использовать существующий script с опцией -0777
: perl -0777pi -e 's/START\s+STOP/REPLACE/sg'
, Добавление /g
по-прежнему необходимо, если вам может потребоваться выполнить несколько замен в файле.
Икота, с которой вы можете столкнуться, хотя и не с этим регулярным выражением: если регулярное выражение было START.+STOP
, а файл содержит несколько пар START/STOP, жадное сопоставление .+
будет потреблять все, начиная с первого START до Последняя остановка. Вы можете использовать не-жадное соответствие (как можно меньше) с помощью .+?
.
Если вы хотите использовать привязки ^
и $
для границ строк в любой точке строки, вам также понадобится модификатор /m
regex.
Ответ 2
Ты был близок. Вам нужно либо -00
, либо -0777
:
perl -0777 -pi -e 's/START\s+/START/' input.txt
Ответ 3
Относительно простой однострочный (чтение файла в памяти):
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
Ответ 4
Здесь однострочный, который не сразу считывает весь файл в память:
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
Ответ 5
perl -MFile::Slurp -e '$content = read_file(shift); $content =~ s/START\s+STOP/REPLACE/s; print $content' input.txt