Grep: Gwiazdka (*) nie zawsze działa

12

Jeśli grepuję dokument zawierający następujące elementy:

ThisExampleString

... dla wyrażenia This*Stringlub *Stringnic nie jest zwracane. Jednak This*zwraca powyżej linii zgodnie z oczekiwaniami.

To, czy wyrażenie jest ujęte w cudzysłów, nie ma znaczenia.

Myślałem, że gwiazdka wskazuje dowolną liczbę nieznanych znaków? Dlaczego działa tylko wtedy, gdy jest na początku wyrażenia? Jeśli jest to zamierzone zachowanie, czego mam używać zamiast wyrażeń This*Stringi *String?

Trae
źródło
ponieważ nie tak działa wyrażenie regularne ... (w szczególności * != any number of unknown characters
:.

Odpowiedzi:

19

Gwiazdka w wyrażeniach regularnych oznacza „dopasuj poprzedni element 0 lub więcej razy”.

W twoim szczególnym przypadku z grep 'This*String' file.txt, próbujesz powiedzieć: „hej, grep, dopasuj do mnie słowo Thi, następnie szero lub więcej małych liter , a następnie słowo String”. sNigdzie nie ma małych liter Example, dlatego grep ignoruje ThisExampleString.

W przypadku: grep '*String' file.txtmówisz „grep, dopasuj do mnie pusty ciąg - dosłownie nic - poprzedzający słowo String”. Oczywiście nie tak ThisExampleStringnależy to czytać. (Istnieją inne możliwe znaczenia - możesz wypróbować to z -Eflagą i bez niej - ale żadne z tych znaczeń nie jest podobne do tego, czego naprawdę chcesz tutaj.)

Wiedząc, że .oznacza „dowolny pojedynczy znak”, możemy to zrobić: grep 'This.*String' file.txt. Teraz polecenie grep odczyta go poprawnie: Thispo nim następuje dowolny znak (pomyśl o nim jako o wyborze znaków ASCII) powtarzany dowolną liczbę razy, a następnie String.

Sergiy Kolodyazhnyy
źródło
6
W Bash (i większości powłok uniksowych) *jest znakiem specjalnym i należy go cytować lub uciec np. W ten sposób: grep 'This*String' file.txtlub to: grep This\*String file.txtaby nie być zaskoczonym nieoczekiwanymi wynikami.
pabouk
2
@pabouk w muszlach, *jest to symbol wieloznaczny. W grep *jest operatorem wyrażeń regularnych. Zobacz unix.stackexchange.com/q/57957/70524
mur
11
pabouk ma rację, rozszerzenie nazwy pliku następuje przed uruchomieniem polecenia; porównaj strace grep .* file.txt |& head -n 1 i strace grep '.*' file.txt |& head -n 1. W rzeczywistości grepdziała również z dowolnymi znakami Unicode (np. echo -ne ⇏ | grep ⇏Wyjściami )
Kos
1
@Serg: masz tutaj dobrą reputację, więc pomyślałem, że natychmiast zauważysz, co mam na myśli. OP oznaczył pytanie bash, więc zakładam, że omawiane polecenia są interpretowane przez bash. Oznacza to, że najpierw bashinterpretuje znaki specjalne i dopiero po wszystkich wykonanych rozszerzeniach przekazuje parametry do odrodzonego procesu. ----- Przykładowo tego polecenia Basha: grep This.\*String file.txtbędzie tarła /bin/grepo tych parametrach: 0 grep, 1: This.*String2: file.txt. Zauważ, że Bash usunął odwrotny ukośnik, a pierwotny znak ucieczki *został przekazany dosłownie.
pabouk
7
Zabawne (a przy rozwiązywaniu problemów dość paskudne :) jest to, że twoje polecenia jak grep This.*String file.txtzwykle będą działać, ponieważ najprawdopodobniej nie będzie pliku pasującego do wyrażenia wieloznacznego powłoki This.*String. W takim przypadku domyślnie Bash przekaże argument dosłownie obejmujący *.
pabouk
8

*Metaznak BRE 1 s, ERE 1 s i pcre 1 s mecze 0 lub więcej wystąpień wcześniej zgrupowanych wzoru (jeśli pogrupowane wzór poprzedzające *metaznaku), 0 lub więcej wystąpień poprzedniego klasy postaci (jeśli klasa postaci jest poprzedzający *metaznak) lub 0 lub więcej wystąpień poprzedniego znaku (jeśli *metaznak nie występuje zgrupowany wzorzec ani klasa znaków );

Oznacza to, że we This*Stringwzorcu, ponieważ *metaznak nie jest poprzedzony ani zgrupowanym wzorcem, ani klasą znaków, *metaznak pasuje do 0 lub więcej wystąpień poprzedniego znaku (w tym przypadku sznak):

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

Aby dopasować 0 lub więcej wystąpień dowolnego znaku, chcesz dopasować 0 lub więcej wystąpień .metaznaku, które pasują do dowolnego znaku:

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

*Metaznak w BRE oraz ERE jest zawsze „chciwy”, czyli będzie on pasował najdłuższy mecz:

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

To może nie być pożądane zachowanie; w przeciwnym razie możesz włączyć grepsilnik PCRE (korzystając z -Popcji) i dołączyć ?metaznak, który po umieszczeniu po metaznakach *i +powoduje zmianę ich chciwości:

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

1: Podstawowe wyrażenia regularne, rozszerzone wyrażenia regularne i wyrażenia regularne zgodne z Perl

kos
źródło
Dziękuję za bardzo pouczającą odpowiedź. Wybrałem jednak inną odpowiedź, ponieważ była krótsza i łatwiejsza do zrozumienia. +1 za dostarczenie tak wielu szczegółów.
Trae
@Trae Nie ma za co. W porządku, zgadzam się, że może to było zbyt skomplikowane i przyjęło zbyt wiele założeń dla kogoś, kto nie jest zbyt obeznany z tym tematem.
Kos
4

Jednym z wyjaśnień znaleźć tutaj odwołuje się :

Gwiazdka „ *” nie oznacza tego samego w wyrażeniach regularnych jak w symbolach wieloznacznych; jest to modyfikator, który stosuje się do poprzedniego pojedynczego znaku lub wyrażenia, takiego jak [0–9]. Gwiazdka odpowiada zero lub więcej z tego, co ją poprzedza. W ten sposób [A-Z]*dopasowuje dowolną liczbę wielkich liter, w tym żadną, a jednocześnie [A-Z][A-Z]*dopasowuje jedną lub więcej wielkich liter.

Ova
źródło
1

*ma specjalne znaczenie zarówno jako znak globowania powłoki („symbol wieloznaczny”), jak i metaznak wyrażenia regularnego . Musisz wziąć to pod uwagę, ale jeśli zacytujesz swoje wyrażenie regularne, możesz zapobiec specjalnemu traktowaniu go przez powłokę i upewnić się, że przekazuje ją bez zmian grep. Chociaż rodzaj podobny koncepcyjnie, jakie *środki do powłoki jest zupełnie inny od tego, co to znaczy grep.

Najpierw powłoka traktuje *jak symbol wieloznaczny.

Powiedziałeś:

To, czy wyrażenie jest ujęte w cudzysłów, nie ma znaczenia.

To zależy od tego, jakie pliki istnieją w jakimkolwiek katalogu, w którym się znajdujesz po uruchomieniu polecenia. W przypadku wzorców zawierających separator katalogów /może to zależeć od plików istniejących w całym systemie. Powinieneś zawsze cytować wyrażenia regularne dla - grepa pojedyncze cudzysłowy są zwykle najlepsze - chyba że jesteś pewien, że nie masz nic przeciwko dziewięciu rodzajom potencjalnie zaskakujących transformacji, które powłoka wykonuje w przeciwnym razie przed wykonaniem greppolecenia.

Gdy powłoka napotka *znak, który nie jest cytowany , oznacza to, że oznacza „zero lub więcej dowolnego znaku” i zastępuje słowo, które go zawiera, listą nazw plików pasujących do wzorca. ( Nazwy plików rozpoczynające się od .są wykluczone - chyba że sam wzorzec zaczyna się od . lub nie skonfigurowałeś powłoki tak, aby i tak je obejmował.) Jest to znane jako globbing - a także przez rozszerzenie nazw plików i rozszerzenie nazw ścieżek .

Efekt z grepzazwyczaj będzie to pierwszy pasujący plik jest traktowane jako wyrażenie regularne - nawet jeśli byłoby to dość oczywiste dla czytelnika ludzkiej, że jest nie oznaczało, jako wyrażenie regularne - gdy wszystkie inne wymienione nazwy plików automatycznie od glob są traktowane jako pliki, w których można wyszukiwać dopasowania. (Nie widzisz listy - jest ona nieprzezroczysta grep.) Właściwie nigdy nie chcesz, aby tak się stało.

Powodem, dla którego czasami nie jest to problem - aw twoim konkretnym przypadku, przynajmniej do tej pory , nie było - jest to, że *pozostaniesz sam, jeśli wszystkie poniższe warunki są prawdziwe :

  1. Nie było żadnych plików, których nazwy pasują do siebie. ... Lub wyłączyłeś globowanie w swojej powłoce, zwykle za pomocą set -flub równoważnego set -o noglob. Ale jest to rzadkie i prawdopodobnie wiedziałbyś, że to zrobiłeś.

  2. Używasz powłoki, której domyślnym zachowaniem jest pozostawienie w *spokoju, gdy nie ma pasujących nazw plików. Tak jest w przypadku Bash, którego prawdopodobnie używasz, ale nie we wszystkich powłokach w stylu Bourne'a. (Domyślne zachowanie w popularnej powłoce Zsh, na przykład, polega na tym, że globusy albo (a) rozwijają się, albo (b) generują błąd.) ... Lub zmieniłeś to zachowanie swojej powłoki - jak to się robi, zmienia się w poprzek muszli.

  3. W przeciwnym razie nie powiedziałeś swojej powłoce, aby zezwoliła na zastąpienie globów niczym, gdy nie ma pasujących plików, ani nie powiodła się z komunikatem o błędzie w tej sytuacji. W Bash można to zrobić, włączając odpowiednio opcjęnullglob lub failglob shell .

Czasami możesz polegać na # 2 i # 3, ale rzadko możesz polegać na # 1. grepKomenda z nienotowanego wzór, który działa teraz może przestać działać, gdy masz różne pliki lub po uruchomieniu go z innego miejsca. Podaj wyrażenie regularne, a problem zniknie.

Wtedy gdy greptraktuje dowodzenia *jako kwantyfikator.

Inne odpowiedzi - takie jak Sergiy Kolodyazhnyy i Kos - również odnoszą się do tego aspektu tego pytania na nieco inne sposoby. Zachęcam więc tych, którzy jeszcze ich nie czytali, do zrobienia tego przed lub po przeczytaniu reszty tej odpowiedzi.

Zakładając, że *robi się grep - co powinno zapewnić cytowanie - grepoznacza to, że poprzedzający go element może wystąpić dowolną liczbę razy , zamiast musieć występować dokładnie raz . To może się zdarzyć raz. Lub może wcale nie być obecny. Lub można to powtórzyć. Tekst pasujący do którejkolwiek z tych możliwości zostanie dopasowany.

Co rozumiem przez „przedmiot”?

  • Pojedyncza postać . Od bmeczów dosłownym b, b*zero lub więcej bs, co ab*codpowiada ac, abc, abbc, abbbc, itd.

    Podobnie, ponieważ .dopasowuje dowolny znak , .*zero lub więcej znaków 1 , co a.*cmeczów ac, akc, ahjglhdfjkdlgjdfkshlgc, nawet acccccchjckhcc, itp Or

  • Klasa postaci . Ponieważ [xy]mecze xlub y, [xy]*dopasowuje zero lub więcej znaków, gdzie każdy z nich jest albo xalbo y, co p[xy]*qpasuje pq, pxq, pyq, pxxq, pxyq, pyxq, pyyq, pxxxq, pxxyq, itd.

    Odnosi się to również do skrótowym formy z klas postaci, takich jak \w, \W, \s, i \S. Ponieważ \wpasuje do dowolnego znaku słowa, \w*dopasowuje zero lub więcej znaków słowa. Lub

  • Grupy . Od \(bar\)meczów bar, \(bar\)*zero lub więcej bars, co foo\(bar\)*bazodpowiada foobaz, foobarbaz, foobarbarbaz, foobarbarbarbaz, itd.

    Za pomocą opcji -Elub traktuje wyrażenie regularne odpowiednio jako ERE lub PCRE , a nie jako BRE , a następnie grupy są otoczone przez zamiast , więc wtedy użyjesz zamiast i zamiast .-Pgrep( )\( \)(bar)\(bar\)foo(bar)bazfoo\(bar\)baz

man greppodaje na końcu przystępne wyjaśnienie składni BRE i ERE, a także listę wszystkich opcji wiersza poleceń, które grepakceptuje na początku. Polecam tę stronę podręcznika jako zasób, a także dokumentację GNU Grep i tę stronę z samouczkami / materiałami referencyjnymi (do której linkowałem na wielu stronach powyżej).

Do testowania i uczenia się grepzalecam nazywanie go wzorcem, ale bez nazwy pliku. Następnie pobiera dane wejściowe z terminala. Wpisz linie; linie, które odbijają się echem, to te, które zawierały tekst pasujący do wzorca. Aby wyjść, naciśnij Ctrl+ Dna początku linii, która sygnalizuje koniec wejścia. (Lub możesz nacisnąć Ctrl+ Cjak w większości programów wiersza poleceń.) Na przykład:

grep 'This.*String'

Jeśli użyjesz --colorflagi, greppodświetli określone części linii, które pasują do wyrażenia regularnego, co jest bardzo przydatne zarówno do ustalenia, co robi wyrażenie regularne, jak i do znalezienia tego, czego szukasz, kiedy to zrobisz. Domyślnie użytkownicy Ubuntu mają alias Bash, który powoduje grep --color=autouruchomienie - co jest wystarczające do tego celu - po uruchomieniu grepz wiersza poleceń, więc prawdopodobnie nie musisz nawet --colorręcznie przekazywać .

1 Dlatego .*w wyrażeniu regularnym oznacza to, co *oznacza w globu powłoki. Różnica polega jednak na tym, że grepautomatycznie drukuje wiersze zawierające dopasowanie w dowolnym miejscu , więc zazwyczaj nie ma potrzeby umieszczania .*na początku lub na końcu wyrażenia regularnego.

Eliah Kagan
źródło