GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Einen Regex extrahieren, der mit „sed“ übereinstimmt, ohne die umgebenden Zeichen auszugeben?

An alle „Sed“-Ärzte da draußen:

Wie können Sie „sed“ dazu bringen, einen regulären Ausdruck zu extrahieren, auf den es in einer
Zeile passt?

Mit anderen Worten, ich möchte nur die Zeichenfolge, die dem regulären
Ausdruck entspricht, wobei alle nicht übereinstimmenden Zeichen aus der enthaltenden Zeile entfernt werden.

Ich habe versucht, die Rückverweisfunktion wie unten zu verwenden

regular expression to be isolated 
         gets `inserted` 
              here     
               |
               v  
 sed -n 's/.*( ).*/1/p 

das funktioniert für einige Ausdrücke wie

 sed -n 's/.*(CONFIG_[a-zA-Z0-9_]*).*/1/p 

das alle Makronamen, die mit „CONFIG_ ….“ beginnen (in irgendeiner „*.h“-Datei gefunden) sauber extrahiert und Zeile für Zeile ausgibt

          CONFIG_AT91_GPIO
          CONFIG_DRIVER_AT91EMAC
                   .
                   .   
          CONFIG_USB_ATMEL
          CONFIG_USB_OHCI_NEW
                   .
                 e.t.c. 

ABER das obige bricht zusammen für so etwas wie

  sed -n 's/.*([0-9][0-9]*).*/1/p 

dies gibt immer einzelne Ziffern wie

zurück
                 7
                 9
                 .
                 .  
                 6

anstatt ein zusammenhängendes Zahlenfeld wie .

zu extrahieren
              8908078
              89670890  
                 .
                 .  
                 .
               23019   
                 .
               e.t.c.  

P.S.:Über Rückmeldungen, wie das in 'sed' erreicht wird, wäre ich dankbar.
Ich weiß, wie das mit 'grep' und 'awk' geht
Ich würde gerne wissen, ob mein – wenn auch begrenzt – das Verständnis von
'sed' hat Lücken und ob es eine Möglichkeit gibt, dies in 'sed' zu tun, die ich
einfach übersehen habe.

Akzeptierte Antwort:

Wenn ein regulärer Ausdruck Gruppen enthält, kann es mehr als eine Möglichkeit geben, einen String damit abzugleichen:Reguläre Ausdrücke mit Gruppen sind mehrdeutig. Betrachten Sie zum Beispiel den regulären Ausdruck ^.*([0-9][0-9]*)$ und die Zeichenfolge a12 . Es gibt zwei Möglichkeiten:

  • Passen Sie a an gegen .* und 2 gegen [0-9]*; 1 entspricht [0-9] .
  • Übereinstimmung mit a1 gegen .* und die leere Zeichenkette gegen [0-9]*; 2 entspricht [0-9] .

Sed wendet, wie alle anderen Regexp-Tools da draußen, die früheste Übereinstimmungsregel an:Es versucht zuerst, den ersten Teil mit variabler Länge mit einer Zeichenfolge abzugleichen, die so lang wie möglich ist. Wenn es einen Weg findet, den Rest der Zeichenfolge mit dem Rest des regulären Ausdrucks abzugleichen, ist das in Ordnung. Andernfalls versucht sed die nächstlängste Übereinstimmung für den ersten Abschnitt mit variabler Länge und versucht es erneut.

Hier ist die Übereinstimmung mit der längsten Zeichenfolge zuerst a1 gegen .* , also stimmt die Gruppe nur mit 2 überein . Wenn Sie möchten, dass die Gruppe früher beginnt, können Sie bei einigen Regexp-Engines .* erstellen weniger gierig, aber sed hat keine solche Funktion. Sie müssen also die Mehrdeutigkeit beseitigen mit etwas zusätzlichem Anker. Geben Sie an, dass der führende .* darf nicht mit einer Ziffer enden, sodass die erste Ziffer der Gruppe die erste mögliche Übereinstimmung ist.

  • Wenn die Zifferngruppe nicht am Zeilenanfang stehen kann:

    sed -n 's/^.*[^0-9]([0-9][0-9]*).*/1/p'
    
  • Wenn die Zifferngruppe am Anfang der Zeile stehen kann und Ihr sed den ? unterstützt Operator für optionale Teile:

    sed -n 's/^(.*[^0-9])?([0-9][0-9]*).*/1/p'
    
  • Wenn die Zifferngruppe am Anfang der Zeile stehen kann, bleiben Sie bei den Standard-Regexp-Konstrukten:

    sed -n -e 's/^.*[^0-9]([0-9][0-9]*).*/1/p' -e t -e 's/^([0-9][0-9]*).*/1/p'
    

Übrigens ist es dieselbe früheste längste Übereinstimmungsregel, die [0-9]* ergibt stimmen mit den Ziffern nach der ersten überein, nicht mit dem nachfolgenden .* .

Beachten Sie, dass Ihr Programm bei mehreren Ziffernfolgen in einer Zeile immer die letzte Ziffernfolge extrahieren wird, wiederum aufgrund der frühesten längsten Übereinstimmungsregel, die auf den anfänglichen .* angewendet wird . Wenn Sie die erste Ziffernfolge extrahieren möchten, müssen Sie angeben, dass das, was davor kommt, eine Folge von Nichtziffern ist.

sed -n 's/^[^0-9]*([0-9][0-9]*).*$/1/p'

Allgemeiner ausgedrückt:Um die erste Übereinstimmung eines regulären Ausdrucks zu extrahieren, müssen Sie die Negation dieses regulären Ausdrucks berechnen. Obwohl dies theoretisch immer möglich ist, wächst die Größe der Negation exponentiell mit der Größe des zu negierenden regulären Ausdrucks, sodass dies oft unpraktisch ist.

Verwandt:SMART-Unterstützung für externe Festplatte kann nicht aktiviert werden?

Betrachten Sie Ihr anderes Beispiel:

sed -n 's/.*(CONFIG_[a-zA-Z0-9_]*).*/1/p'

Dieses Beispiel weist tatsächlich das gleiche Problem auf, aber Sie sehen es nicht bei typischen Eingaben. Wenn Sie es füttern hello CONFIG_FOO_CONFIG_BAR , dann druckt der obige Befehl CONFIG_BAR aus , nicht CONFIG_FOO_CONFIG_BAR .

Es gibt eine Möglichkeit, die erste Übereinstimmung mit sed zu drucken, aber es ist ein wenig knifflig:

sed -n -e 's/(CONFIG_[a-zA-Z0-9_]*).*/n1/' -e T -e 's/^.*n//' -e p

(Angenommen, Ihr sed unterstützt n um einen Zeilenumbruch in den s zu bedeuten Ersetzungstext.) Dies funktioniert, weil sed nach der frühesten Übereinstimmung des regulären Ausdrucks sucht und wir nicht versuchen, das zu finden, was vor CONFIG_… steht Bit. Da es innerhalb der Zeile keinen Zeilenumbruch gibt, können wir ihn als temporäre Markierung verwenden. Das T Befehl sagt aufzugeben, wenn der vorangehende s Befehl stimmte nicht überein.

Wenn Sie nicht herausfinden können, wie Sie etwas in sed tun sollen, wenden Sie sich an awk. Der folgende Befehl gibt die früheste längste Übereinstimmung eines regulären Ausdrucks aus:

awk 'match($0, /[0-9]+/) {print substr($0, RSTART, RLENGTH)}'

Und wenn Sie es einfach halten möchten, verwenden Sie Perl.

perl -l -ne '/[0-9]+/ && print $&'       # first match
perl -l -ne '/^.*([0-9]+)/ && print $1'  # last match

Linux
  1. Löschen Sie die ersten fünf Zeichen in einer beliebigen Zeile einer Textdatei unter Linux mit sed

  2. Was ist falsch an meiner Lookahead-Regex in GNU sed?

  3. Wie kann ich grep zum Abgleichen verwenden, ohne die Übereinstimmungen zu drucken?

  4. Regex-Gruppierung stimmt mit der C++ 11-Regex-Bibliothek überein

  5. Drucken von der Befehlszeile mit LibreOffice, lpr-Befehlen?

Ergänzung mit „sed“?

Müssen Regex-Zeichen in Sed maskiert werden, um als Regex-Zeichen interpretiert zu werden?

Alle Dateien bis zum Match anzeigen?

Verwenden des tr-Befehls in Linux, um mit Charakteren zu spielen

Finden Sie unter Linux Dateien mit illegalen Windows-Zeichen im Namen

Verwendung von find und tar mit Dateien mit Sonderzeichen im Namen