Wie kann die Gier eines regulären Ausdrucks in AWK reduziert werden?

14

Ich möchte einen nicht gierigen Mustervergleich (regulärer Ausdruck) durchführen awk. Hier ist ein Beispiel:

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

Ist es möglich, einen regulären Ausdruck zu schreiben, der die kürzere Zeichenfolge auswählt?

@article{gjn,

anstelle dieser langen Zeichenfolge ?:

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

Ich möchte dieses Ergebnis erhalten:

 Author =   {Grzegorz J. Nalepa},



Ich habe ein anderes Beispiel:

echo " , article {gjn, Author = {Grzegorz J. Nalepa}," | awk '{sub (/ , [^,] *, /, ""); drucken }'
      ↑ ↑ ^^^^^

Beachten Sie, dass ich die @Zeichen an ,der ersten Stelle sowohl der Eingabezeichenfolge als auch des regulären Ausdrucks in Komma ( ) geändert habe (und auch in geändert .*habe [^,]*). Ist es möglich, einen regulären Ausdruck zu schreiben, der die kürzere Zeichenfolge auswählt?

, Author =   {Grzegorz J. Nalepa},

anstelle der längeren Saite ?:

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

Ich möchte dieses Ergebnis erhalten:

,article{gjn
nowy1
quelle
4
Genauso wie reguläre Ausdrücke für robustes HTML-Parsen nicht geeignet sind, können sie diese Art von kontextsensitivem Grammatik-Parsen wahrscheinlich nicht ausführen. Wenn Ihre Eingaben jedoch ziemlich eingeschränkt und wohlgeformt sind, können Sie möglicherweise mit Regex davonkommen, solange Sie Ihre Einschränkungen angeben. Zum Beispiel können Sie für aussehen könnten Authornach einem Komma und Leerzeichen, gefolgt von einem Leerzeichen gefolgt von =gefolgt von Leerzeichen , gefolgt von {gefolgt von einem beliebigen nicht }gefolgt }, obwohl dies (unter anderem) erfordert , dass Sie nicht verschachtelt werden können {}innerhalb des = { ... }Teils.
JW013
@jw013, danke für deine Erklärung. Dennoch werde ich auf Anregungen anderer User warten.
nowy1

Antworten:

18

Wenn Sie @und bis zu dem ersten ,danach auswählen möchten , müssen Sie es als angeben@[^,]*,

Darauf @folgt eine beliebige Anzahl ( *) von Nicht-Kommas ( [^,]), gefolgt von einem Komma ( ,).

Dieser Ansatz funktioniert als Äquivalent zu @.*?,, aber nicht für Dinge wie @.*?string, bei denen das, was danach ist, mehr als ein einzelnes Zeichen ist. Das Negieren eines Zeichens ist einfach, aber das Negieren von Zeichenfolgen in regulären Ausdrücken ist viel schwieriger .

Ein anderer Ansatz besteht darin, Ihre Eingabe vorab zu verarbeiten, um das stringZeichen durch ein Zeichen zu ersetzen oder voranzustellen, das ansonsten in Ihrer Eingabe nicht vorkommt:

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

Wenn Sie nicht garantieren können, dass die Eingabe kein Ersatzzeichen enthält (siehe \1oben), können Sie einen Escape-Mechanismus verwenden:

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")

Das funktioniert für feste strings, aber nicht für beliebige reguläre Ausdrücke wie für das Äquivalent von @.*?foo.bar.

Stéphane Chazelas
quelle
Vielen Dank für die gute Resonanz. Bei meiner Bearbeitung habe ich noch ein weiteres Beispiel gefragt (siehe meine Bearbeitung).
nowy1
6

Es gibt bereits mehrere gute Antworten, die Workarounds für awkdie Unfähigkeit bieten, nicht-gierige Übereinstimmungen durchzuführen. Daher gebe ich einige Informationen zu einer alternativen Möglichkeit, dies mit Perl-kompatiblen regulären Ausdrücken (PCRE) zu tun . Beachten Sie, dass die meisten einfachen "Match and Print" -Skripten mit der Befehlszeilenoptionawk einfach neu implementiert perlwerden -nkönnen und komplexere Skripten mit dem a2p Awk to Perl-Übersetzer konvertiert werden können .

Perl hat einen nicht-gierigen Operator, der in Perl-Skripten und in allem, was PCRE verwendet, verwendet werden kann. Zum Beispiel auch in der -POption von GNU grep implementiert .

PCRE ist nicht identisch mit Perls regulären Ausdrücken, aber es ist sehr nah. Es ist eine beliebte Wahl für eine Bibliothek mit regulären Ausdrücken für viele Programme, da es sehr schnell ist und die Perl-Verbesserungen für erweiterte reguläre Ausdrücke sehr nützlich sind.

Aus der perlre (1) -Manpage :

   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
quelle
3

Dies ist ein alter Beitrag, aber die folgenden Informationen könnten für andere hilfreich sein.

Es gibt eine zugegebenermaßen grobe Möglichkeit, in awk einen nicht gierigen RE-Abgleich durchzuführen. Die Grundidee besteht darin, die Match-Funktion (String, RE) zu verwenden und die Größe des Strings schrittweise zu verringern, bis der Match fehlschlägt, etwa (ungetestet):

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
quelle
2

Für allgemeine Ausdrücke kann dies als nicht gierige Übereinstimmung verwendet werden:

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
}

Ich benutze dies basierend auf der Antwort von @ JimMellander. smatchverhält sich wie match:

die Position, s an der der reguläre Ausdruck rvorkommt, oder 0, falls dies nicht der Fall ist. Die Variablen RSTARTund RLENGTHwerden auf die Position und Länge der übereinstimmenden Zeichenfolge gesetzt.

ericbn
quelle
1

Awk bietet keine Möglichkeit, nicht gierige Übereinstimmungen durchzuführen. Möglicherweise können Sie jedoch die gewünschte Ausgabe erhalten. Der Vorschlag von sch wird für diese Zeile funktionieren. Wenn Sie sich nicht auf ein Komma verlassen können, sondern "Autor" immer der Anfang von dem ist, was Sie wollen, können Sie dies tun:

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

Wenn die Anzahl der Zeichen vor dem Autor immer gleich ist, können Sie dies tun:

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

Sie müssen nur wissen, wie Ihre Daten im gesamten Satz aussehen.

user17591
quelle
0

Es gibt immer einen Weg. Das gegebene Problem kann ziemlich einfach gelöst werden, indem Kommas als Trennzeichen verwendet werden.

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

Wenn die Anzahl der Felder variiert, wird normalerweise etwas Besseres benötigt. In solchen Fällen lohnt es sich oft, ein Stoppwort zu finden, da Sie damit alles aus der Zeile herausschneiden können. Im Kontext des Beispiels ist hier, was ich mit Stoppwörtern meine.

echo "@article{gjn2010jucs, Author =   {Grzegorz J. Nalepa}, " |
awk  '{sub(/.*Author/, "Author", $0); sub(/},.*/, "}", $0); print $0}'
Kerolasa
quelle
0

Ich weiß, dass dies ein alter Beitrag ist. Aber hier ist etwas, das awk nur wie gewünscht als OP verwendet:
A = @ article {gjn2010jucs, Author = {Grzegorz J. Nalepa},
echo $ A | awk 'sub (/ @ [^,] * /, "")'

Ausgabe:,
Autor = {Grzegorz J. Nalepa},

VINAY NAIR
quelle
1
Diese Antwort ist aus ungefähr fünf Gründen falsch.
Scott
3
Kannst du mir bitte helfen zu verstehen, was los ist? Die Ausgabe scheint mit der Anforderung übereinzustimmen. Ich versuche zu verstehen, warum die Antwort richtig / nicht richtig ist.
VINAY NAIR