Вопрос или проблема
Единственный файл в текущем рабочем каталоге называется test.txt
и его содержимое просто:
Это маленький тестовый файл.
- Выполнение
grep -in * -e 'te.?t file'
даёт нет совпадений.
- Но экранирование
?
работает:grep -in * -e 'te.\?t file'
даёт совпадение
Сумасшествие: С звездочкой *
всё наоборот!
- Без экранирования:
grep -in * -e 'te.*t file'
даёт совпадение
- и экранирование
*
не работает:grep -in * -e 'te.\*t file'
нет совпадений
Почему мета-символы ?
и *
обрабатываются по-разному в отношении экранирования?
Это просто синтаксическая спецификация, используемая grep
. Без дополнительных флагов (GNU) grep
использует Базовые Регулярные Выражения, которые требуют экранирования некоторых мета-символов (в своей чистой форме(1), фигурные скобки). Также смотрите страницу man по grep
:
Базовые против Расширенных Регулярных Выражений
В базовых регулярных выражениях мета-символы
?
,+
,{
,|
,(
, и)
теряют своё особое значение; вместо этого используйте экранированные версии\?
,\+
,\{
,\|
,\(
, и\)
.
Это означает, что в режиме BRE ?
должен быть экранирован, если он должен обрабатываться как мета-символ, в то время как для *
это не так. Для более “интуитивного” поведения вы можете переключить grep
в режим Расширенных Регулярных Выражений (ERE):
~$ grep -E 'te.?t file' test.txt
Это маленький тестовый файл.
~$ grep -E 'te.\?t file' test.txt
~$ grep -E 'te.*t file' test.txt
Это маленький тестовый файл.
~$ grep -E 'te.\*t file' test.txt
(1) Обратите внимание, что \|
, \+
и \?
в режиме BRE фактически являются нестандартными расширениями, но поддерживаются GNU и Busybox grep
(и, возможно, другими). Также смотрите ответ Эли Минкоффа.
Кроме того, как предложил @Kusalananda, sed
может помочь в отладке RegExp:
~$ sed -E 's/te.?t file/(&)/' test.txt
Это маленький (тестовый файл).
grep
по умолчанию использует Базовые Регулярные Выражения, которые являются более ограниченной диалектом и не поддерживают операторы ?
или +
. Реализации GNU и FreeBSD grep
поддерживают их, если они экранированы обратной косой чертой, но это нестандартно. Если вы хотите, чтобы grep
использовал Расширенные Регулярные Выражения, которые являются более современным диалектом, поддерживающим их, вы можете вызвать его с флагом -E
:
grep -in * -E -e 'te.?t file'
Или, с конкретно GNU grep,
grep -in * --extended-regexp -e 'te.?t file'
Ответ или решение
Когда мы работаем с командой grep
, важно понимать разницу между базовыми и расширенными регулярными выражениями, так как именно это определяет поведение определённых метасимволов. В вашем случае вопрос о том, почему символ ?
нужно экранировать, а .
и *
— нет, связан с трактовкой этих символов в контексте регулярных выражений.
Суть проблемы
По умолчанию grep
использует синтаксис базовых регулярных выражений (BRE — Basic Regular Expressions). В этой системе определённые метасимволы, такие как ?
, теряют своё специальное значение и требуют экранирования, чтобы правильно обработаться как метасимволы.
Почему ?
нужно экранировать
Символ ?
в контексте регулярных выражений означает "ноль или одно вхождение предыдущего символа". Однако в использовании базовых регулярных выражений GNU grep
этот символ не приобретает своих специальных свойств, если не будет экранирован. То есть, чтобы grep
правильно интерпретировал этот символ как метасимвол, необходимо использовать обратный слэш: \?
.
Пример:
grep -in * -e 'te.\?t file'
Этот запрос даст вам соответствие, поскольку \?
правильно интерпретируется.
Почему .
и *
не нужно экранировать
Символы .
и *
имеют свои специальные значения в базе регулярных выражений и, следовательно, могут использоваться без экранирования. .
означает "любой символ", а *
значит "ноль или более вхождений предыдущего символа". Эти символы остаются метасимволами независимо от формата или контекста, в котором они используются. Таким образом, можете использовать их напрямую, и grep
понимает их значение без дополнительных указаний.
Пример:
grep -in * -e 'te.*t file'
Этот запрос будет корректно интерпретирован grep
и найдет совпадение.
Разница в использовании
Как вы заметили, поведение этих символов отличается в зависимости от контекста. Когда вы используете *
и не экранируете его, grep
воспримет его как метасимвол и выполнит поиск корректно. Если же вы экранируете *
, то он перестает быть метасимволом и grep
будет искать именно символ *
в вашем файле, что и приводит к отсутствию совпадений.
Переход на расширенные регулярные выражения
Чтобы избежать путаницы с экранированием символов, вы можете использовать расширенные регулярные выражения (ERE — Extended Regular Expressions). В этом случае ?
будет восприниматься как метасимвол без необходимости экранирования. Для использования расширенных регулярных выражений в grep
вы можете использовать флаг -E
:
grep -in * -E -e 'te.?t file'
Согласно этому подходу, поведение станет более интуитивно понятным, так как большинство пользователей ожидают, что такие символы, как ?
и *
, будут работать как они и задумывались в контексте регулярных выражений.
Заключение
Подводя итоги, можно сказать, что различное поведение ?
и *
в grep
связано с тем, как именно реализованы базовые регулярные выражения. Для большей гибкости и удобства работы с регулярными выражениями рекомендуется использовать расширенные регулярные выражения, что значительно упростит взаимодействие с символами и уберет необходимость в постоянном экранировании некоторых из них.