Греп: Зірочка (*) не завжди працює


12

Якщо я зірвую документ, який містить таке:

ThisExampleString

... за виразом This*Stringабо *String, нічого не повертається. Однак This*повертає вищезазначений рядок як очікувалося.

Незалежно від того, чи є вираз укладеним у лапки, немає значення.

Я думав, що зірочка вказує будь-яку кількість невідомих символів? Чому це працює лише якщо він знаходиться на початку виразу? Якщо це призначена поведінка, що я використовую замість виразів This*Stringі *String?


тому що це не так, як працює * != any number of unknown characters
геджекс

Відповіді:


19

Зірочка в регулярних виразах означає "відповідати попередньому елементу 0 або більше разів".

У вашому конкретному випадку grep 'This*String' file.txtви намагаєтесь сказати: "Ей, греп, зрівняй мене зі словом Thi, після чого з невеликим sнулем або більше разів, за ним слово String". Нижній регістр sніде не зустрічається Example, отже, grep ігнорує ThisExampleString.

У випадку grep '*String' file.txt, якщо ви говорите, "греп, зрівняйся з порожньою рядком - буквально нічого - перед словом String". Звичайно, це не так, як ThisExampleStringслід читати. (Є й інші можливі значення - ви можете спробувати це з -Eпрапором і без нього, - але жодне зі значень не схоже на те, що ви насправді хочете тут.)

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


6
У Bash (і більшість оболонок Unix) *особливий персонаж, і його слід цитувати або уникати, наприклад, так: grep 'This*String' file.txtабо це: grep This\*String file.txtщоб не дивуватися несподіваним результатам.
пабук

2
@pabouk в оболонках, *це підстановка. З грепом *- оператор регулярного вираження. Дивіться unix.stackexchange.com/q/57957/70524
муру

11
pabouk вірно, розширення імені файлів відбувається перед запуском команди; порівняти 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: grep This.\*String file.txtпороджуватиме /bin/grepз цими параметрами 0: grep1: This.*String2: file.txt. Зауважте, що Баш зняв зворотну косу рису і спочатку втік *був переданий буквально.
пабук

7
Найсмішніша (і для усунення несправностей досить неприємна :) річ у тому, що ваші команди, як grep This.*String file.txt, звичайно, працюватимуть, тому що, швидше за все, не буде файлу, що відповідає виразному символу оболонки оболонки This.*String. У такому випадку за замовчуванням Bash передасть аргумент буквально включаючи *.
pabouk

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двигун PCRE (використовуючи -Pопцію) і додати ?метахарактрису, яка при нанесенні після *і +метахарактерів може змінити свою жадібність:

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

1: Основні регулярні вирази, розширені регулярні вирази та регулярні вирази, сумісні з Perl


Дякую за дуже інформативну відповідь. Однак я вибрав іншу відповідь, тому що вона була коротшою та легшою для розуміння. +1 за надання такої кількості деталей.
Trae

@Trae Безкоштовно Це добре, я погоджуюся, що, можливо, це було занадто складно і робило занадто багато припущень для того, хто не надто знайомий з цією темою.
kos

4

Одне з пояснень знайдено тут за посиланням :

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


1

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

Спочатку оболонку розглядають *як підстановку.

Ти сказав:

Незалежно від того, чи є вираз укладеним у лапки, немає значення.

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

Коли оболонка стикається з *символом, який не цитується , він вважає, що це означає «нуль або більше будь-якого символу» і замінює слово, яке містить його, списком імен файлів, що відповідають шаблону. (Імена файлів, які починаються з ., виключаються - якщо тільки ваш шаблон не починається з . або ви не налаштували оболонку, щоб все-таки включити їх.) Це відомо як глобування - а також за розширенням імені файлів та розширенням імені шляху .

Ефект з grep, як правило, полягає в тому, що перше ім'я файлу, що відповідає, приймається як регулярний вираз - навіть якщо людському читачеві було б цілком очевидно, що він не мається на увазі як регулярний вираз - в той час як усі інші імена файлів автоматично перераховані з вашого glob приймаються як файли, всередині яких для пошуку відповідностей. (Ви не бачите списку - він непрозоро передається grep.) Ви практично ніколи не хочете, щоб це сталося.

Причина цього іноді не є проблемою - а у вашому конкретному випадку, принаймні, поки що це не було - це те *, що залишиться в спокої, якщо всі наступні дійсні :

  1. Там не було ні одного файлу , чиї імена збігаються. ... Або ви відключили глобулінг у своїй оболонці, як правило, з set -fеквівалентом або set -o noglob. Але це нечасто, і ви, мабуть, знаєте, що це зробили.

  2. Ви використовуєте оболонку, поведінка за замовчуванням якої залишається в *спокої, коли немає відповідних імен файлів. Це той випадок у Bash, який ви, мабуть, використовуєте, але не у всіх оболонках у стилі Борна. (Поведінка за замовчуванням у популярній оболонці Zsh, наприклад, для глобусів або (a) розширюється, або (b) створює помилку.) ... Або ви змінили таку поведінку вашої оболонки - як це робиться, змінюється поперек снарядів.

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

Іноді можна покластися на №2 та №3, але рідко можна покластися на №1. 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автоматично друкується рядки, які містять вашу відповідність будь-де в них, тому зазвичай .*на початку або в кінці регулярного виразу зазвичай не потрібно .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.