Extrahieren eines Regex, der mit 'sed' übereinstimmt, ohne die umgebenden Zeichen zu drucken

24

An alle 'sed'-Ärzte da draußen:

Wie können Sie sed dazu bringen, einen regulären Ausdruck zu extrahieren, der mit einer Zeile übereinstimmt?

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 

Dies funktioniert für einige Ausdrücke wie

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

Dadurch werden alle Makronamen, die mit 'CONFIG_ ....' beginnen (in einigen '* .h'-Dateien enthalten), sauber extrahiert und zeilenweise ausgedruckt

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

ABER das obige bricht für so etwas zusammen

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

Dies liefert immer einzelne Ziffern wie

                 7
                 9
                 .
                 .  
                 6

anstatt ein fortlaufendes Zahlenfeld wie.

              8908078
              89670890  
                 .
                 .  
                 .
               23019   
                 .
               e.t.c.  

PS: Ich wäre dankbar für Feedback, wie dies in "sed" erreicht wird. Ich weiß , wie dies zu tun mit ‚grep‘ und ‚awk‘ würde Ich mag, um herauszufinden , ob mein - wenn auch begrenzte - das Verständnis von ‚sed‘ hat Löcher und wenn es Weg , dies in ‚sed‘ zu tun , was ich
habe einfach übersehen.

Darbehdar
quelle

Antworten:

22

Wenn ein regulärer Ausdruck Gruppen enthält, kann es mehrere Möglichkeiten geben, eine Zeichenfolge damit abzugleichen: reguläre Ausdrücke mit Gruppen sind nicht eindeutig. Betrachten Sie beispielsweise den regulären Ausdruck ^.*\([0-9][0-9]*\)$und die Zeichenfolge a12. Es gibt zwei Möglichkeiten:

  • Spiel agegen .*und 2gegen [0-9]*; 1passt zu [0-9].
  • Spiel a1gegen .*und die leere Zeichenfolge gegen [0-9]*; 2passt zu [0-9].

Sed wendet wie alle anderen regulären Ausdrücke die früheste Regel für die längste Übereinstimmung an: Zunächst wird versucht, den ersten Teil variabler Länge mit einer möglichst langen Zeichenfolge abzugleichen. Wenn es einen Weg findet, den Rest der Zeichenkette mit dem Rest des regulären Ausdrucks abzugleichen, ist das in Ordnung. Andernfalls versucht sed die nächstlängste Übereinstimmung für den ersten Teil variabler Länge und versucht es erneut.

Hier ist die Übereinstimmung mit der längsten Zeichenfolge zuerst a1dagegen .*, sodass die Gruppe nur übereinstimmt 2. Wenn Sie möchten, dass die Gruppe früher startet, können Sie mit einigen Regexp-Engines .*weniger gierig werden, aber sed hat keine solche Funktion. Sie müssen also die Mehrdeutigkeit mit einem zusätzlichen Anker beseitigen . Geben Sie an, dass die führende .*Ziffer nicht mit einer Ziffer enden darf, 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 sich die Zifferngruppe am Anfang der Zeile befinden kann und Ihre sed den \?Operator für optionale Teile unterstützt:

    sed -n 's/^\(.*[^0-9]\)\?\([0-9][0-9]*\).*/\1/p'
    
  • Wenn sich die Zifferngruppe am Anfang der Zeile befinden kann, halten Sie sich an reguläre reguläre Ausdrücke:

    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]*die Ziffern nach der ersten und nicht nach der folgenden übereinstimmt .*.

Beachten Sie, dass Ihr Programm bei mehreren Ziffernfolgen in einer Zeile immer die letzte Ziffernfolge extrahiert, da die Regel mit der frühesten längsten Übereinstimmung auf die Initiale angewendet wird .*. Wenn Sie die erste Folge von Ziffern extrahieren möchten, müssen Sie angeben, dass eine Folge von Nicht-Ziffern vorkommt.

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

Um die erste Übereinstimmung eines regulären Ausdrucks zu extrahieren, müssen Sie im Allgemeinen die Negation dieses regulären Ausdrucks berechnen. Während dies theoretisch immer möglich ist, wächst die Größe der Negation exponentiell mit der Größe des zu negierenden regulären Ausdrucks, was häufig unpraktisch ist.

Betrachten Sie Ihr anderes Beispiel:

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

In diesem Beispiel tritt das gleiche Problem auf, das jedoch bei typischen Eingaben nicht auftritt. Wenn Sie es füttern hello CONFIG_FOO_CONFIG_BAR, wird der obige Befehl gedruckt CONFIG_BAR, nicht CONFIG_FOO_CONFIG_BAR.

Es gibt eine Möglichkeit, das erste Match mit sed auszudrucken, aber es ist ein bisschen knifflig:

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

(Angenommen, Ihre sed-Unterstützung \nbedeutet eine neue Zeile im sErsetzungstext.) Dies funktioniert, weil sed nach der frühesten Übereinstimmung des regulären Ausdrucks sucht und wir nicht versuchen, die Übereinstimmung zu finden, die dem CONFIG_…Bit vorausgeht . Da sich in der Zeile kein Zeilenumbruch befindet, können wir ihn als temporären Marker verwenden. Der TBefehl sagt aufgeben, wenn der vorhergehende sBefehl nicht übereinstimmt.

Wenn Sie nicht wissen, wie man etwas in sed macht, 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)}'

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
Gilles 'SO - hör auf böse zu sein'
quelle
22

Während dies nicht sedder Fall ist grep -o, ist eines der Dinge, die oft übersehen werden , was meiner Meinung nach das bessere Werkzeug für diese Aufgabe ist.

Wenn Sie beispielsweise alle CONFIG_Parameter aus einer Kernelkonfiguration abrufen möchten, verwenden Sie Folgendes:

# grep -Eo 'CONFIG_[A-Z0-9_]+' config
CONFIG_64BIT
CONFIG_X86_64
CONFIG_X86
CONFIG_INSTRUCTION_DECODER
CONFIG_OUTPUT_FORMAT

Wenn Sie zusammenhängende Zahlenfolgen erhalten möchten:

$ grep -Eo '[0-9]+' foo
Patrick
quelle
7
sed '/\n/P;//!s/[0-9]\{1,\}/\n&\n/;D'

... erledigt dies ohne viel Aufhebens, obwohl Sie möglicherweise wörtliche Zeilenumbrüche anstelle der ns im Substitutionsfeld rechts benötigen . .*CONFIGÜbrigens würde das Ding nur funktionieren, wenn es nur einen Treffer auf der Leitung gäbe - sonst würde es immer nur den letzten bekommen.

Sie können dies für eine Beschreibung der Funktionsweise sehen, aber dies wird in einer separaten Zeile nur so oft gedruckt, wie es in einer Zeile vorkommt.

Sie können dieselbe Strategie verwenden, um das [num]dritte Vorkommen in einer Zeile zu ermitteln. Wenn Sie beispielsweise die CONFIG-Übereinstimmung nur dann drucken möchten, wenn es sich um die dritte in einer Zeile handelt:

sed '/\n/P;//d;s/CONFIG[[:alnum:]]*/\n&\n/3;D'

... obwohl davon ausgegangen wird, dass die CONFIGZeichenfolgen bei jedem Auftreten durch mindestens ein nicht alphanumerisches Zeichen getrennt sind.

Ich nehme an - für die Zahlensache - das würde auch funktionieren:

sed -n 's/[^0-9]\{1,\}/\n/g;s/\n*\(.*[0-9]\).*/\1/p

... mit dem gleichen Vorbehalt wie zuvor über die rechte Hand \n. Dieser wäre sogar schneller als der erste, kann aber offensichtlich nicht so allgemein angewendet werden.

Für die CONFIG-Sache können Sie die P;...;Dobige Schleife mit Ihrem Muster verwenden, oder Sie können Folgendes tun:

sed -n 's/[^C]*\(CONFIG[[:alnum:]]*\)\{0,1\}C\{0,1\}/\1\n/g;s/\(\n\)*/\1/g;/C/s/.$//p'

... was nur ein bisschen aufwändiger ist und bei korrekter Bestellung sedder Referenzpriorität funktioniert . Außerdem werden alle CONFIG-Übereinstimmungen in einer Zeile auf einmal isoliert. Dabei wird jedoch die gleiche Annahme wie zuvor getroffen, dass jede CONFIG-Übereinstimmung durch mindestens ein nicht-alphanumerisches Zeichen getrennt wird. Mit GNU sedkönnte man es schreiben:

sed -En 's/[^C]*(CONFIG\w*)?C?/\1\n/g;s/(\n)*/\1/g;/C/s/.$//p'
mikeserv
quelle