Замечания:
* Python используется для иллюстрации поведения, но этот вопрос является языковым агностиком.
* Для целей этого обсуждения предположим только однострочный ввод, потому что наличие новых строк (многострочный ввод) вводит изменения в поведении $
и .
которые связаны с вопросами.
Большинство двигателей регулярных выражений:
-
принять регулярное выражение, которое явно пытается сопоставить выражение после конца строки ввода [1].
$ python -c "import re; print(re.findall('$.*', 'a'))" [''] # !! Matched the hypothetical empty string after the end of 'a'
-
при поиске/замене в глобальном масштабе, т.е. при поиске всех неперекрывающихся совпадений данного регулярного выражения и, достигнув конца строки, неожиданно попытайтесь снова сопоставить [2] как объяснено в этом ответе на связанный с ним вопрос:
$ python -c "import re; print(re.findall('.*$', 'a'))" ['a', ''] # !! Matched both the full input AND the hypothetical empty string
Возможно, нет необходимости говорить, что такие попытки сопоставления преуспевают только в том случае, если соответствующее регулярное выражение соответствует пустой строке (а регулярное выражение по умолчанию/настроено для сообщения совпадений нулевой длины).
Такое поведение, по крайней мере, на первый взгляд противоречит интуиции, и мне интересно, может ли кто-то дать им обоснование дизайна, не в последнюю очередь потому, что:
- неясно, в чем польза такого поведения.
- наоборот, в контексте поиска/замены на глобальном уровне шаблонов, таких как
.*
и.*$
, поведение совершенно неожиданно. [3]- Чтобы задать вопрос более остро: почему функциональность, предназначенная для поиска нескольких совпадающих совпадений регулярного выражения, т.е. Глобального соответствия, решает даже попробовать другое совпадение, если оно знает, что весь вход уже потреблен, независимо от того, что регулярное выражение (хотя вы никогда не увидите симптом с регулярным выражением, которое по крайней мере не соответствует пустой строке)
- Следующие языки/двигатели демонстрируют удивительное поведение:.NET, Python (и 2.x и 3.x) [2] Perl (оба 5.x и 6.x), Ruby, Node.js(JavaScript)
Обратите внимание, что двигатели регулярных выражений отличаются поведением относительно того, где продолжить сопоставление после соответствия нулевой длины (пустой строки).
Любой выбор (начало в той же позиции символа и начало в следующем) является оправданным - см. Главу об ошибках нулевой длины на www.regular-expressions.info.
Напротив, обсуждаемый здесь случай .*$
Отличается тем, что с любым непустым вводом первое совпадение для .*$
Не соответствует нулевой длине, поэтому разница в поведении не применяется - вместо этого позиция символа должны безоговорочно продвигаться после первого матча, что, конечно, невозможно, если вы уже в конце.
Опять же, мое удивление заключается в том, что другой матч все же пытается, хотя там по определению ничего не осталось.
[1] Я использую $
качестве маркера конца ввода, хотя в некоторых машинах, таких как.NET, он может пометить конец конца ввода необязательно, за которым следует конечная новая строка.Однако поведение одинаково применимо, если вы используете безусловный маркер конца ввода, \z
.
[2] Python 2.x и 3.x до 3.6.x, по-видимому, особый -c подход к замещению в этом контексте: python -c "import re; print(re.sub('.*$', '[\g<0>]', 'a'))"
используемый для получения только [a]
- т.е. было найдено и заменено только одно совпадение.
Начиная с Python 3.7, поведение теперь похоже на большинство других механизмов регулярных выражений, где выполняются две замены, что дает [a][]
.
[3] Вы можете избежать проблемы либо (a) выбором метода замены, который предназначен для поиска не более одного совпадения, либо (b) использовать ^.*
Чтобы предотвратить совпадение нескольких совпадений с помощью привязки начала ввода.
(a) не может быть вариантом, в зависимости от того, как определенная функциональность языковых поверхностей;например, оператор PowerShell -replace
неизменно заменяет все вхождения;рассмотрите следующую попытку вложить все элементы массива в "..."
:'a', 'b' -replace '.*', '"$&"'
.Из-за совпадения в два раза это дает элементы "a"""
и "b"""
;
опция (b), 'a', 'b' -replace '^.*', '"$&"'
, исправляет проблему.