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.*
und2
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