Jak lub dlaczego użycie `. *?` Jest lepsze niż `. *`?

9

Odpowiedziałem na to pytanie na SuperUser, które było związane z rodzajem wyrażeń regularnych używanych podczas grepowania wyniku.

Odpowiedź, którą podałem, była następująca:

 tail -f log | grep "some_string.*some_string"

A potem w trzech komentarzach do mojej odpowiedzi @Bob napisał:

.*jest chciwy i może uchwycić więcej, niż chcesz. .*?jest zwykle lepszy.

Wtedy to,

?jest modyfikator na *, co leniwy zamiast chciwy domyślnie. Zakładając PCRE.

Poszukałem wyszukiwarki Google PCRE, ale nie mogłem zrozumieć, co to ma znaczyć w mojej odpowiedzi?

i na koniec to

Powinienem również zauważyć, że jest to wyrażenie regularne (domyślnie grep robi wyrażenie regularne POSIX), a nie glob powłoki.

Wiem tylko, czym jest Regex i bardzo podstawowe użycie go w poleceniu grep. Tak więc nie mogłem uzyskać żadnego z tych 3 komentarzy i mam na myśli następujące pytania:

  • Jakie są różnice w wykorzystaniu .*?vs .*?
  • Które jest lepsze i w jakich okolicznościach? Proszę podać przykłady.

Pomocne byłoby również zrozumienie komentarzy, gdyby ktokolwiek mógł


AKTUALIZACJA: Jako odpowiedź na pytanie Czym Regex różni się od Shell Globs? @Kusalananda podał ten link w swoim komentarzu.

UWAGA: W razie potrzeby przeczytaj moją odpowiedź na to pytanie, zanim odpowiesz na pytanie dotyczące kontekstu.

C0deDaedalus
źródło
To są dwa bardzo różne pytania. Na pierwsze pytanie odpowiada unix.stackexchange.com/questions/57957/..., natomiast na drugie pytanie zależy od zastosowania wzorca (nie można powiedzieć, że jest „lepsze” w każdych okolicznościach).
Kusalananda
Możesz edytować to pytanie, aby dotyczyło tylko problemu .*vs. .*?Pytanie o „różnicę między wyrażeniami regularnymi a globami powłoki” zostało już rozwiązane na tej stronie.
Kusalananda

Odpowiedzi:

7

Ashok już zauważył różnicę między .*i .*?, więc będę tylko podać kilka dodatkowych informacji.

grep (przy założeniu, że wersja GNU) obsługuje 4 sposoby dopasowania ciągów:

  • Naprawiono ciągi znaków
  • Podstawowe wyrażenia regularne (BRE)
  • Rozszerzone wyrażenia regularne (ERE)
  • Wyrażenia regularne zgodne z Perlem (PCRE)

grep domyślnie używa BRE.

BRE i ERE są udokumentowane w rozdziale Wyrażenia regularne POSIX, a PCRE jest udokumentowany na jego oficjalnej stronie internetowej . Należy pamiętać, że funkcje i składnia mogą się różnić w zależności od implementacji.

Warto powiedzieć, że ani BRE, ani ERE nie wspierają lenistwa :

Zachowanie wielu sąsiadujących symboli powielania („+”, „*”, „?” I przedziały) daje niezdefiniowane wyniki.

Więc jeśli chcesz skorzystać z tej funkcji, musisz zamiast tego użyć PCRE:

# BRE greedy
$ grep -o 'c.*s' <<< 'can cats eat plants?'
can cats eat plants

# BRE lazy
$ grep -o 'c.*\?s' <<< 'can cats eat plants?'
can cats eat plants

# ERE greedy
$ grep -E -o 'c.*s' <<< 'can cats eat plants?'
can cats eat plants

# ERE lazy
$ grep -E -o 'c.*?s' <<< 'can cats eat plants?'
can cats eat plants

# PCRE greedy
$ grep -P -o 'c.*s' <<< 'can cats eat plants?'
can cats eat plants

# PCRE lazy
$ grep -P -o 'c.*?s' <<< 'can cats eat plants?'
can cats

Edytuj 1

Czy mógłbyś wyjaśnić trochę na temat .*vs .*??

  • .*służy do dopasowania „najdłuższego” 1 możliwego wzoru.

  • .*?służy do dopasowania „najkrótszego” 1 możliwego wzoru.

Z mojego doświadczenia wynika, że ​​najbardziej pożądanym zachowaniem jest zwykle drugie.

Załóżmy na przykład, że mamy następujący ciąg znaków i chcemy dopasować tylko tagi HTML 2 , a nie treść między nimi:

<title>My webpage title</title>

Teraz porównaj .*vs .*?:

# Greedy
$ grep -P -o '<.*>' <<< '<title>My webpage title</title>'
<title>My webpage title</title>

# Lazy
$ grep -P -o '<.*?>' <<< '<title>My webpage title</title>'
<title>
</title>

1. Znaczenie „najdłuższego” i „najkrótszego” w kontekście wyrażenia regularnego jest nieco trudne, jak zauważył Kusalananda . Więcej informacji znajduje się w oficjalnej dokumentacji.
2. Nie zaleca się analizowania html z regex . To tylko przykład do celów edukacyjnych, nie używaj go w produkcji.

nxnev
źródło
Czy mógłbyś wyjaśnić trochę na temat .*vs .*??
C0deDaedalus
@ C0deDaedalus Zaktualizowano.
nxnev
9

Załóżmy, że biorę ciąg taki jak:

can cats eat plants?

Użycie chciwości c.*sspowoduje dopasowanie całego ciągu, ponieważ zaczyna się od ci kończy na s, będąc chciwym operatorem, kontynuuje dopasowanie aż do końcowego wystąpienia s.

Natomiast użycie leniwego c.*?sbędzie pasować tylko do momentu sznalezienia pierwszego wystąpienia , tzn can cats. Ciągu .

Z powyższego przykładu możesz być w stanie zebrać, że:

„Chciwy” oznacza dopasowanie jak najdłuższego ciągu. „Leniwy” oznacza dopasowanie możliwie najkrótszego ciągu. Dodawanie ?do kwantyfikatora jak *, +, ?, lub {n,m}robi to leniwe.

Ashok Arora
źródło
1
Byłoby to „najkrótsze możliwe” cats, więc nie wymusza to ściśle „najkrótszego możliwego”.
Kusalananda
2
@Kusalananda prawda, nie ściśle w tym sensie, ale „najkrótszy możliwy” oznacza tutaj między pierwszym wystąpieniem zarówno c i s.
Ashok Arora,
1

Ciąg można dopasować na kilka sposobów (od prostych do bardziej złożonych):

  1. Jako ciąg statyczny (Załóżmy, że var = „Hello World!”):

    [ "$var" = "Hello World!" ] && echo yes
    echo "$var" | grep -F "Hello"
    grep -F "Hello" <<<"$var"

  2. Jako glob:

    echo ./* # wyświetla listę wszystkich plików w pwd.
    case $var in (*Worl*) echo yes;; (*) echo no;; esac
    [[ "$var" == *"Worl"* ]] && echo yes

    Istnieją podstawowe i rozszerzone globusy. W caseprzykładzie wykorzystano podstawowe globusy. [[Przykład bash używa rozszerzonych globów. Pierwsze dopasowanie pliku może być podstawowe lub rozszerzone w niektórych powłokach, takich jak ustawienie extglobw bash. Oba są w tym przypadku identyczne. Grep nie mógł używać globów.

    Gwiazdka w glob oznacza coś innego niż gwiazdka w wyrażeniu regularnym :

    * matches any number (including none) ofdowolne postacie .
    * matches any number (including none) of thepoprzedzający element .

  3. Jako podstawowe wyrażenie regularne (BRE):

    echo "$var" | sed 's/W.*d//' # print: Cześć!
    grep -o 'W.*d' <<<"$var" # print Świat!

    Nie ma BRE w (podstawowych) powłokach ani awk.

  4. Rozszerzone wyrażenia regularne (ERE):

    [[ "$var" =~ (H.*l) ]] # match: Hello Worl
    echo "$var" | sed -E 's/(d|o)//g' # print: Hell Wrl!
    awk '/W.*d/{print $1}' <<<"$var" # print: Hello
    grep -oE 'H.*l' <<<"$var" # print: Hello Worl

  5. Wyrażenia regularne kompatybilne z Perlem:

    grep -oP 'H.*?l # print: Hel

Tylko w PCRE a *?ma określone znaczenie składniowe.
To sprawia, że ​​gwiazdka jest leniwa (niewdzięczność): lenistwo zamiast chciwości .

$ grep -oP 'e.*l' <<<"$var"
ello Worl

$ grep -oP 'e.*?l' <<<"$var"
el

To tylko wierzchołek góry lodowej, są zachłanni, leniwi , uległi lub pozytywni . Istnieją również lookahhead i lookbeind, ale nie dotyczą one gwiazdki *.

Istnieje alternatywa, aby uzyskać taki sam efekt, jak niechciane wyrażenie regularne:

$ grep -o 'e[^o]*o' <<<"$var"
ello

Pomysł jest bardzo prosty: nie używaj kropki ., zaneguj następny znak do dopasowania [^o]. Z tagiem internetowym:

$ grep -o '<[^>]*>' <<<'<script type="text/javascript">document.write(5 + 6);</script>'
<script type="text/javascript">
</script>

Powyższe powinno całkowicie wyjaśnić wszystkie komentarze @Bob 3. Parafrazowanie:

  • A. * jest powszechnym wyrażeniem regularnym, a nie globem.
  • Tylko regex może być kompatybilny z PCRE.
  • W PCRE: a? zmodyfikuj * kwantyfikator. .*jest chciwy .*?nie jest.

pytania

  • Jakie są różnice w użytkowaniu. ? vs. . ?

    • A .*?jest poprawne tylko w składni PCRE.
    • A .*jest bardziej przenośny.
    • Taki sam efekt, jak w przypadku chciwego dopasowania, można uzyskać, zastępując kropkę negowanym zakresem znaków: [^a]*
  • Które jest lepsze i w jakich okolicznościach? Proszę podać przykłady.
    Lepszy? To zależy od celu. Nie ma nic lepszego, każdy jest przydatny do różnych celów. Podałem kilka przykładów powyżej. Potrzebujesz więcej?

Izaak
źródło