Jak zmniejszyć chciwość wyrażeń regularnych w AWK?

14

Chcę wykonać niepochodny wzór (wyrażenie regularne) awk. Oto przykład:

echo "@article{gjn, Author =   {Grzegorz J. Nalepa}, " | awk '{ sub(/@.*,/,""); print }'

Czy można napisać wyrażenie regularne, które wybiera krótszy ciąg?

@article{gjn,

zamiast tego długiego łańcucha ?:

@article{gjn, Author =   {Grzegorz J. Nalepa},

Chcę uzyskać ten wynik:

 Author =   {Grzegorz J. Nalepa},



Mam inny przykład:

echo ” , artykuł {gjn, Autor = {Grzegorz J. Nalepa}," | awk '{sub (/ , [^,] *, /, ""); drukuj}
      ↑ ↑ ^^^^^

Zauważ, że zmieniłem @znaki na przecinek ( ,) na pierwszej pozycji zarówno ciągu wejściowego, jak i wyrażenia regularnego (a także zmieniłem .*na [^,]*). Czy można napisać wyrażenie regularne, które wybiera krótszy ciąg?

, Author =   {Grzegorz J. Nalepa},

zamiast dłuższego ciągu ?:

,article{gjn, Author =   {Grzegorz J. Nalepa},

Chcę uzyskać ten wynik:

,article{gjn
nowy1
źródło
4
Podobnie jak wyrażenia regularne nie są odpowiednie do solidnego parsowania HTML, prawdopodobnie nie będą w stanie wykonać tego rodzaju kontekstowego analizowania gramatyki. Jeśli jednak twój zestaw danych wejściowych jest dość ograniczony i dobrze uformowany, możesz być w stanie uciec od wyrażenia regularnego, pod warunkiem, że podasz swoje ograniczenia. Na przykład możesz poszukać Authorprzecinka i białych znaków, a następnie białych znaków, a =następnie białych znaków, a {następnie dowolnych znaków, po których nie }następuje }, chociaż wymaga to (między innymi), że nie można zagnieździć {}wewnątrz tej = { ... }części.
jw013
@ jw013, dziękuję za wyjaśnienie. Będę jednak czekał na sugestie innych użytkowników.
nowy1

Odpowiedzi:

18

Jeśli chcesz wybrać, @a później do pierwszego ,, musisz określić jako@[^,]*,

To jest @następuje dowolny numer ( *) bezczynności przecinkami ( [^,]) przecinek ( ,).

Takie podejście działa jak ekwiwalent @.*?,, ale nie w przypadku takich rzeczy @.*?string, w których to, co jest później, to więcej niż jedna postać. Negowanie postaci jest łatwe, ale negowanie ciągów wyrażeń regularnych jest o wiele trudniejsze .

Innym podejściem jest wstępne przetworzenie danych wejściowych w celu zastąpienia lub uzupełnienia stringznakiem, który inaczej nie występuje w danych wejściowych:

gsub(/string/, "\1&") # pre-process
gsub(/@[^\1]*\1string/, "")
gsub(/\1/, "") # revert the pre-processing

Jeśli nie możesz zagwarantować, że dane wejściowe nie będą zawierać twojego zamiennego znaku ( \1powyżej), jednym z podejść jest użycie mechanizmu zmiany znaczenia:

gsub(/\1/, "\1\3") # use \1 as the escape character and escape itself as \1\3
                   # in case it's present in the input
gsub(/\2/, "\1\4") # use \2 as our maker character and escape it
                   # as \1\4 in case it's present in the input
gsub(/string/, "\2&") # mark the "string" occurrences

gsub(/@[^\2]*\2string/, "")

# then roll back the marking and escaping
gsub(/\2/, "")
gsub(/\1\4/, "\2")
gsub(/\1\3/, "\1")

Działa to dla stałych strings, ale nie dla dowolnych wyrażeń regularnych, jak dla odpowiednika @.*?foo.bar.

Stéphane Chazelas
źródło
Dziękuję bardzo za dobrą odpowiedź. W mojej edycji poprosiłem o jeszcze jeden przykład (patrz moja edycja).
nowy1
6

Istnieje już kilka dobrych odpowiedzi zapewniających obejście problemu awkniezdolności do wykonywania chciwych dopasowań, dlatego podaję informacje o alternatywnym sposobie zrobienia tego przy użyciu wyrażeń regularnych zgodnych z Perl (PCRE). Zauważ, że najprostsze awkskrypty „dopasuj i wydrukuj” można łatwo zaimplementować ponownie perlza pomocą opcji -nwiersza poleceń, a bardziej złożone skrypty można przekonwertować za pomocą awk a2p na translator Perl.

Perl ma niewdzięczny operator, którego można używać w skryptach Perla i we wszystkim, co korzysta z PCRE. Na przykład, również zaimplementowany w opcji GNU grep -P.

PCRE nie jest identyczny z wyrażeniami regularnymi Perla, ale jest bardzo blisko. Jest to popularny wybór biblioteki wyrażeń regularnych dla wielu programów, ponieważ jest bardzo szybki, a rozszerzenia Perla do rozszerzonych wyrażeń regularnych są bardzo przydatne.

Ze strony podręcznika perlre (1) :

   By default, a quantified subpattern is "greedy", that is, it will match
   as many times as possible (given a particular starting location) while
   still allowing the rest of the pattern to match.  If you want it to
   match the minimum number of times possible, follow the quantifier with
   a "?".  Note that the meanings don't change, just the "greediness":

       *?        Match 0 or more times, not greedily
       +?        Match 1 or more times, not greedily
       ??        Match 0 or 1 time, not greedily
       {n}?      Match exactly n times, not greedily (redundant)
       {n,}?     Match at least n times, not greedily
       {n,m}?    Match at least n but not more than m times, not greedily
cas
źródło
3

To jest stary post, ale poniższe informacje mogą być przydatne dla innych.

Istnieje sposób, co prawda prymitywne, na wykonanie niechcianego dopasowania RE w awk. Podstawową ideą jest użycie funkcji dopasowania (ciąg, RE) i stopniowe zmniejszanie rozmiaru ciągu, aż dopasowanie się nie powiedzie, coś w stylu (nieprzetestowane):

if (match(string, RE)) {
    rstart = RSTART
    for (i=RLENGTH; i>=1; i--)
        if (!(match(substr(string,1,rstart+i-1), RE))) break;
    # At this point, the non-greedy match will start at rstart
    #  for a length of i+1
}
Jim Mellander
źródło
2

W przypadku wyrażeń ogólnych można tego użyć jako niepochodnego dopasowania:

function smatch(s, r) {
    if (match(s, r)) {
        m = RSTART
        do {
            n = RLENGTH
        } while (match(substr(s, m, n - 1), r))
        RSTART = m
        RLENGTH = n
        return RSTART
    } else return 0
}

Używam tego na podstawie odpowiedzi @ JimMellander. smatchzachowuje się jak match, zwracając:

pozycja, w s której rwystępuje wyrażenie regularne , lub 0, jeśli nie występuje. Zmienne RSTARTi RLENGTHsą ustawione w pozycji i długości dopasowanej ciąg.

ericbn
źródło
1

W awk nie ma sposobu na dopasowanie bez chciwości. Możesz jednak uzyskać pożądaną moc wyjściową. sugestia sch będzie działać dla tej linii. Jeśli nie możesz polegać na przecinku, ale „Autor” jest zawsze początkiem tego, czego chcesz, możesz to zrobić:

awk '{ sub(/@.*Author/,"Author"); print }'

Jeśli liczba znaków poprzedzających autora jest zawsze taka sama, możesz to zrobić:

awk '{ sub(/@.{21}/,""); print }'

Musisz tylko wiedzieć, jak wyglądają Twoje dane w całym zestawie.

użytkownik17591
źródło
0

Zawsze jest rozwiązanie. Dany problem można rozwiązać dość łatwo, używając przecinków jako separatora.

echo "@article{gjn2010jucs, Author =   {Grzegorz J. Nalepa}, " |
awk -F, '{sub(/^[ \t]/, "", $2); print $2}'

Kiedy liczba pól jest różna, zwykle potrzebne jest coś nieco lepszego. W takim przypadku znalezienie słów stop często się opłaca, ponieważ można wyciąć wszystko z linii, używając ich. W kontekście tego przykładu rozumiem słowa stop.

echo "@article{gjn2010jucs, Author =   {Grzegorz J. Nalepa}, " |
awk  '{sub(/.*Author/, "Author", $0); sub(/},.*/, "}", $0); print $0}'
Kerolasa
źródło
0

Wiem, że to stary post. Ale tutaj jest coś, co używa awk jako OP zgodnie z żądaniem:
A = @ artykuł {gjn2010jucs, Autor = {Grzegorz J. Nalepa},
echo $ A | awk 'sub (/ @ [^,] * /, "")'

Wyjście:,
Autor = {Grzegorz J. Nalepa},

NAAY VINAY
źródło
1
Ta odpowiedź jest błędna z około pięciu powodów.
Scott
3
Czy możesz mi pomóc zrozumieć, co jest nie tak? Dane wyjściowe wydają się zgodne z żądanymi. Próbowanie zrozumienia, dlaczego odpowiedź jest poprawna / nieprawidłowa.
VINAY NAIR