Почему это регулярное выражение работает по-разному в sed, чем в Perl/Ruby?
У меня есть регулярное выражение, которое дает мне один результат в sed
, но другой в Perl (и Ruby).
У меня есть строка one;two;;three
, и я хочу выделить подстроки, разделенные символом ;
. Поэтому в Perl я делаю следующее:
$a = "one;two;;three";
$a =~ s/([^;]*)/[\1]/g;
print $a;
(Или в Ruby: print "one;two;;three".gsub(/([^;]*)/, "[\\1]")
.)
Результат:
[one][];[two][];[];[three][]
(Я знаю причину ложных пустых подстрок.)
Любопытно, что когда я запускаю одно и то же регулярное выражение в sed
, получается другой результат. Я запустил:
echo "one;two;;three" | sed -e 's/[^;]*/[\0]/g'
и я получаю:
[one];[two];[];[three]
В чем причина этого другого результата?
EDIT:
Кто-то ответил "потому что sed
не perl
". Я знаю это. Причина, по которой я задаю свой вопрос, заключается в том, что я не понимаю, насколько sed
справляется с нулевыми совпадениями.
Ответы
Ответ 1
Из исходного кода для sed-4.2
для функции-заменителя:
/sed/execute.c
/* If we're counting up to the Nth match, are we there yet?
And even if we are there, there is another case we have to
skip: are we matching an empty string immediately following
another match?
This latter case avoids that baaaac, when passed through
s,a*,x,g, gives `xbxxcx' instead of xbxcx. This behavior is
unacceptable because it is not consistently applied (for
example, `baaaa' gives `xbx', not `xbxx'). */
Это указывает на то, что поведение, которое мы наблюдаем в Ruby и Perl, сознательно избегалось в sed
. Это не связано с какой-либо принципиальной разницей между языками, а результатом специальной обработки в sed
Ответ 2
Это интересный и удивительный краевой случай.
Ваш шаблон [^;]*
может соответствовать пустой строке, поэтому он становится вопросом философии, а именно, сколько пустых строк находится между двумя символами: ноль, один или много?
СЕПГ
Символы sed
четко следуют философии, описанной в разделе "Продвижение после нулевого размера регулярного выражения" "Маски регулярных выражений нулевой длины" .
Теперь двигатель регулярных выражений находится в сложной ситуации. Просили его пройти через всю строку, чтобы найти все совпадающие регулярные выражения. Первый матч закончился в начале строки, где началась первая попытка совпадения. Механизм регулярных выражений нуждается в способе избежать застревания в бесконечном цикле, который навсегда находит одинаковое совпадение нулевой длины в начале строки.
Самое простое решение, которое используется большинством двигателей регулярных выражений, заключается в том, чтобы начать следующую попытку совпадения с одним символом после окончания предыдущего совпадения, если предыдущее совпадение было нулевой длиной.
То есть нулевые пустые строки находятся между символами.
Вышеприведенный отрывок не является авторитетным стандартом, и цитирование такого документа вместо этого сделает это лучшим ответом.
Проверяя источник GNU sed
, мы видим
/* Start after the match. last_end is the real end of the matched
substring, excluding characters that were skipped in case the RE
matched the empty string. */
start = offset + matched;
last_end = regs.end[0];
Perl и Ruby
Философия Perls с s///
, которую Ruby, похоже, разделяет, поэтому в документации и примерах ниже Perl отображает оба параметра - есть ли ровно одна пустая строка после каждого символа.
"Regexp Quote-Like Operators" в документации perlop читает
Модификатор /g
указывает глобальное сопоставление шаблонов, то есть сопоставление как можно больше в строке.
Выполнение трассировки s/([^;]*)/[\1]/g
дает
-
Start. "Позиция соответствия", обозначенная символом ^
, находится в начале целевой строки.
o n e ; t w o ; ; t h r e e
^
-
Попытка сопоставить [^;]*
.
o n e ; t w o ; ; t h r e e
^
Обратите внимание, что результат, полученный в $1
, равен one
.
-
Попытка сопоставить [^;]*
.
o n e ; t w o ; ; t h r e e
^
Важный урок: Цендер regex *
всегда преуспевает, потому что он означает "ноль или больше". В этом случае подстрока в $1
является пустой строкой.
Остальная часть матча продолжается, как указано выше.
Будучи проницательным читателем, вы теперь спрашиваете себя: "Я, если *
всегда преуспевает, как совпадение заканчивается в конце целевой строки или, если на то пошло, как она проходит даже первый нуль -length match?"
Мы находим ответ на этот резкий вопрос в разделе "Повторяющиеся шаблоны, соответствующие подстроке нулевой длины" документации perlre.
Однако большой опыт показал, что многие задачи программирования могут быть значительно упрощены с помощью повторных подвыражений, которые могут соответствовать подстрокам нулевой длины. Вот простой пример:
@chars = split //, $string; # // is not magic in split
($whitewashed = $string) =~ s/()/ /g; # parens avoid magic s// /
Таким образом, Perl допускает такие конструкции, насильственно разбивая бесконечный цикл. Правила для этого отличаются для циклов нижнего уровня, заданных жадными кванторами *+{}
, и для более высоких уровней, таких как модификатор /g
или split
.
& hellip;
Контуры более высокого уровня сохраняют дополнительное состояние между итерациями: было ли последнее совпадение нулевым. Чтобы разбить цикл, следующее совпадение после совпадения нулевой длины запрещается иметь длину нуля. Этот запрет взаимодействует с backtracking & hellip; и поэтому второе наилучшее совпадение выбирается, если наилучшее совпадение имеет нулевую длину.
Другие подходы Perl
С добавлением отрицательного утверждения lookbehind вы можете отфильтровать ложные пустые совпадения.
$ perl -le '$a = "one;two;;three";
$a =~ s/(?<![^;])([^;]*)/[\1]/g;
print $a;'
[one];[two];[];[three]
Примените то, что Mark Dominus назвал Randals Rule: "Используйте захват, когда вы знаете, что хотите сохранить. Используйте split
, когда вы знаете, что вы хотите выбросить". Вы хотите выбросить точки с запятой, поэтому ваш код станет более прямым с
$ perl -le '$a = "one;two;;three";
$a = join ";", map "[$_]", split /;/, $a;
print $a;'
[one];[two];[];[three]
Ответ 3
В сценариях perl (и предположительно рубине) что-то происходит в этом выпуске, нет смысла просто обращаться с регулярным выражением как BRE или ERE.
awk (EREs) и sed (BREs) ведут себя так, как они должны для выполнения замены RE:
$ echo "one;two;;three" | sed -e 's/[^;]*/[&]/g'
[one];[two];[];[three]
$ echo "one;two;;three" | awk 'gsub(/[^;]*/,"[&]")'
[one];[two];[];[three]
Ты сказал I know the reason for the spurious empty substrings.
. Упоминайте нас?