Grep: звездочка (*) не всегда работает

11

Если я grep документ, который содержит следующее:

ThisExampleString

... для выражения This*Stringили *Stringничего не возвращается. Однако This*возвращает вышеуказанную строку, как и ожидалось.

Независимо от того, заключено ли выражение в кавычки, не имеет значения.

Я думал, что звездочкой указано любое количество неизвестных символов? Почему это работает, только если это в начале выражения? Если это предполагаемое поведение, что я использую вместо выражений This*Stringи *String?

Trae
источник
потому что это не то, как работают регулярные выражения ... (в частности:. * != any number of unknown charactersчитать документ.)
njzk2

Ответы:

18

Звездочка в регулярных выражениях означает «соответствовать предыдущему элементу 0 или более раз».

В вашем конкретном случае grep 'This*String' file.txtвы пытаетесь сказать: «Привет, grep, сопоставь мне слово Thi, затем строчные sноль или более раз, а затем слово String». Строчных букв sнигде нет Example, поэтому grep игнорирует ThisExampleString.

В случае grep '*String' file.txt, если вы говорите «grep, сопоставьте мне пустую строку - буквально ничего - предшествующую слову String». Конечно, это не так, как ThisExampleStringпредполагается читать. (Существуют и другие возможные значения - вы можете попробовать это с -Eфлагом и без него, но ни одно из значений не имеет ничего общего с тем, что вы действительно хотите здесь.)

Зная , что .означает «любой символ», мы могли бы сделать это: grep 'This.*String' file.txt. Теперь команда grep будет читать его правильно: Thisза ним следует любой символ (воспринимается как выделение символов ASCII), повторяемый любое количество раз, а затем String.

Сергей Колодяжный
источник
6
В Bash (и большинстве оболочек Unix) *это специальный символ, и его следует заключать в кавычки или экранировать, например, так: grep 'This*String' file.txtили this: grep This\*String file.txtчтобы не удивляться неожиданным результатам.
Пабук
2
@pabouk в оболочках, *подстановочный знак. В grep *это оператор регулярного выражения. См. Unix.stackexchange.com/q/57957/70524
Muru
11
Пабук прав, расширение имени файла происходит до запуска команды; сравнить strace grep .* file.txt |& head -n 1 и strace grep '.*' file.txt |& head -n 1. Также фактически grepработает также с любым символом Unicode (например, echo -ne ⇏ | grep ⇏выходами )
kos
1
@Serg: у вас высокая репутация, поэтому я подумал, что вы сразу заметите, что я имею в виду. ОП пометил вопрос bash, поэтому я предполагаю, что обсуждаемые команды интерпретируются как bash. Это означает, что сначала bashинтерпретирует свои специальные символы и только после всех выполненных расширений передает параметры в порожденный процесс. ----- Например , эта команда в Bash: grep This.\*String file.txtбудет порождать /bin/grepс этими параметрами 0: grep1: This.*String2: file.txt. Обратите внимание, что Bash удалил обратную косую черту, и первоначально сбежавший *был передан буквально.
Пабук
7
Забавно (и для устранения проблем довольно неприятно :) то, что ваши команды like, как grep This.*String file.txtправило, будут работать, потому что, скорее всего, не будет файла, соответствующего шаблонному выражению оболочки This.*String. В таком случае по умолчанию Bash передаст аргумент буквально в том числе *.
Пабук
8

*Метасимвол в BRE 1 с, ERE 1 с и PCRE 1 совпадений с 0 или более вхождений ранее сгруппированных рисунка (если сгруппированы шаблон , предшествующие *метасимвол), 0 или более вхождений предыдущего класса символов (если класс персонажа предшествующий *метасимволу) или 0 или более вхождений предыдущего символа (если *метасимвол не предшествует ни сгруппированному шаблону, ни классу символов );

Это означает, что в This*Stringшаблоне, который является *метасимволом, которому не предшествует ни сгруппированный шаблон, ни класс символов, *метасимвол соответствует 0 или более вхождениям предыдущего символа (в данном случае sсимвола):

% cat infile               
ThisExampleString
ThisString
ThissString
% grep 'This*String' infile
ThisString
ThissString

Чтобы сопоставить 0 или более вхождений любого символа, вы хотите сопоставить 0 или более вхождений .метасимвола, который соответствует любому символу:

% cat infile               
ThisExampleString
% grep 'This.*String' infile
ThisExampleString

*Метасимвол в Бре и EREs всегда «жадный», т.е. он будет соответствовать самому длинному матчу:

% cat infile
ThisExampleStringIsAString
% grep -o 'This.*String' infile
ThisExampleStringIsAString

Это может быть не желаемое поведение; в случае , если это не так , вы можете включить grep«s PCRE двигатель ( с помощью -Pопции) и добавьте ?метасимвол, который когда ставится после *и +метасимволы имеет эффект изменения их алчность:

% cat infile
ThisExampleStringIsAString
% grep -Po 'This.*?String' infile
ThisExampleString

1. Основные регулярные выражения, расширенные регулярные выражения и регулярные выражения, совместимые с Perl

кос
источник
Спасибо за очень информативный ответ. Однако я выбрал другой ответ, потому что он был короче и легче для понимания. +1 за предоставление так много деталей.
Trae
@ Trae Добро пожаловать. Хорошо, я согласен, что, возможно, это было слишком сложно и сделало слишком много предположений для кого-то, кто не слишком знаком с этой темой.
Кос
4

Одно из найденных здесь объяснений ссылка :

Звездочка " *" не означает то же самое в регулярных выражениях, что и в подстановочных знаках; это модификатор, который применяется к предыдущему одиночному символу или выражению, например [0-9]. Звездочка соответствует нулю или более того, что ей предшествует. Таким образом, [A-Z]*сопоставляется любое количество букв в верхнем регистре, включая ни одного, в то время как [A-Z][A-Z]*соответствует одной или нескольким заглавным буквам.

Яйцеклетки
источник
1

*имеет особое значение как в качестве символа для подстановки оболочки («подстановочный знак»), так и в качестве метасимвола регулярного выражения . Вы должны принять во внимание и то и другое, хотя, если вы заключите в кавычки свое регулярное выражение, вы можете помешать оболочке обработать его специально и убедиться, что оно передает его без изменений grep. Хотя это похоже на концептуально, что *означает для оболочки весьма отличается от того, что это значит grep.

Сначала оболочка рассматривается *как подстановочный знак.

Вы сказали:

Независимо от того, заключено ли выражение в кавычки, не имеет значения.

Это зависит от того, какие файлы существуют в любом каталоге, в котором вы оказались при запуске команды. Для шаблонов, которые содержат разделитель каталогов /, это может зависеть от того, какие файлы существуют во всей вашей системе. Вы всегда должны заключать в кавычки регулярные выражения для grep- и одинарные кавычки обычно лучше - если вы не уверены, что у вас все в порядке с девятью типами потенциально удивительных преобразований, которые оболочка выполняет перед выполнением grepкоманды.

Когда оболочка сталкивается с *символом, который не заключен в кавычки , он принимает значение «ноль или более любого символа» и заменяет содержащее его слово на список имен файлов, соответствующих шаблону. (Имена файлов , которые начинаются с .исключены - если ваш шаблон сам по себе не начинается с . или . Вы настроили свою оболочку в любом случае , чтобы включить их) Это известный как подстановка --и также и имена расширения имен файлов и имен файлов .

Эффект с grepобычно состоит в том, что первое подходящее имя файла принимается как регулярное выражение - даже если для читателя-человека будет совершенно очевидно, что оно не подразумевается как регулярное выражение - в то время как все остальные имена файлов перечислены автоматически из вашего glob - это файлы, внутри которых можно искать совпадения. (Вы не видите список - он передается непрозрачно grep.) Вы практически никогда не хотите, чтобы это произошло.

Причина этого иногда не проблема - и в вашем конкретном случае, по крайней мере , до сих пор , это wasn't - это то , что *останется в одиночестве , если все следующие условия :

  1. Там не было ни одного файла , чьи имена совпадают. ... Или вы отключили глобирование в своей оболочке, как правило, с помощью set -fили эквивалентного set -o noglob. Но это необычно, и вы, вероятно, знаете, что сделали это.

  2. Вы используете оболочку, чье поведение по умолчанию - оставить *один, когда нет подходящих имен файлов. Это тот случай в Bash, который вы, вероятно, используете, но не во всех оболочках в стиле Борна. (Поведение по умолчанию в популярной оболочке Zsh, например, для глобусов либо (а) раскрывает, либо (б) выдает ошибку.) ... Или вы изменили это поведение вашей оболочки - как это делается, варьируется через снаряды.

  3. В противном случае вы не указали своей оболочке разрешать замену глобусов ничем, когда не найдено подходящих файлов, или сообщать об ошибке в этой ситуации. В Bash это можно было бы сделать, включив параметр nullglobили failglob shell соответственно.

Иногда вы можете положиться на № 2 и № 3, но вы редко можете положиться на № 1. grepКоманда с некотируемым рисунком , который работает в настоящее время может перестать работать , если у вас есть разные файлы или при запуске его из другого места. Процитируйте свое регулярное выражение, и проблема исчезнет.

Тогда как grepкоманда рассматривает *как квантор.

Другие ответы - такие, как Сергей Колодяжный и Кос - также касаются этого аспекта этого вопроса, несколько по-другому. Поэтому я призываю тех, кто их еще не читал, делать это либо до, либо после прочтения оставшейся части этого ответа.

Предполагая, что это *действительно делает grep - что должно обеспечить цитирование - grepзатем подразумевает, что элемент, предшествующий ему, может встречаться любое количество раз , вместо того, чтобы встречаться ровно один раз . Это все еще может произойти один раз. Или это может вообще не присутствовать. Или это может быть повторено. Текст, который соответствует любой из этих возможностей, будет сопоставлен.

Что я имею в виду под «предметом»?

  • Единый персонаж . Так bматчи буквальный b, b*соответствует нулю или более bS, таким образом , ab*cсоответствует ac, abc, abbc, abbbcи т.д.

    Аналогично, так как .соответствует любому символу , .*соответствует нулю или более символов 1 , при этом a.*cсовпадения ac, akc, ahjglhdfjkdlgjdfkshlgc, даже acccccchjckhcc, и т.д. Или

  • Классовый характер . Так как [xy]спички xили y, [xy]*соответствует нулю или более символов , где каждый из них является либо xили y, таким образом , p[xy]*qсоответствует pq, pxq, pyq, pxxq, pxyq, pyxq, pyyq, pxxxq, pxxyqи т.д.

    Это также относится и к обсчитывать формы классов персонажей , как \w, \W, \sи \S. Так как \wсоответствует любому символу слова, \w*соответствует нулю или более символов слова. Или

  • Группа . Так как \(bar\)матчи bar, \(bar\)*соответствует нулю или более barлет, таким образом , foo\(bar\)*bazсоответствует foobaz, foobarbaz, foobarbarbaz, foobarbarbarbazи т.д.

    С опциями -Eили -P, grepваше регулярное выражение обрабатывается как ERE или PCRE соответственно, а не как BRE , и затем группы окружаются ( )вместо \( \), так что тогда вы будете использовать (bar)вместо \(bar\)и foo(bar)bazвместо foo\(bar\)baz.

man grepдает достаточно доступное объяснение синтаксиса BRE и ERE в конце, а также перечисляет все параметры командной строки, grepпринимаемые в начале. Я рекомендую эту страницу руководства в качестве ресурса, а также документацию по GNU Grep и этот учебный / справочный сайт (на который я ссылаюсь на несколько страниц выше).

Для тестирования и обучения grepя рекомендую называть его шаблоном, но без имени файла. Затем он принимает данные от вашего терминала. Введите строки; Отраженные вам строки - это те строки, которые содержали текст, соответствующий вашему шаблону. Чтобы выйти, нажмите Ctrl+ Dв начале строки, которая сигнализирует о конце ввода. (Или вы можете нажать Ctrl+, Cкак и в большинстве программ командной строки.) Например:

grep 'This.*String'

Если вы используете --colorфлаг, grepто выделите определенные части ваших строк, которые соответствуют вашему регулярному выражению, что очень полезно как для выяснения того, что делает регулярное выражение, так и для поиска того, что вы ищете, как только вы это сделаете. По умолчанию пользователи Ubuntu имеют псевдоним Bash, который grep --color=autoзапускается - что достаточно для этой цели - при запуске grepиз командной строки, поэтому вам, вероятно, даже не нужно проходить --colorвручную.

1 Следовательно, .*в регулярном выражении означает, что *означает в глобусе оболочки. Однако различие заключается в том, grepчто в любом месте автоматически печатаются строки, содержащие ваше совпадение , поэтому обычно нет необходимости иметь .*начало или конец регулярного выражения.

Элия ​​Каган
источник