sed und RegEx Crashkurs

  • Dies ist die Fortsetzung von Runtime eines Bashskriptes optimieren


    Um nun mehrere solcher Zeichen verwenden zu können, ohne solche Ausdrücke endlos oft zu wiederholen, gibt es auch Bereichsoperatoren.
    Ein * heißt, dass der DIREKT vorhergehende Ausdruck 0-mal oder beliebig oft vorkommen kann.
    Ein [At3]* würde also beliebig lange Zeichenketten, die nur die Zeichen A, t, oder 3 in beliebiger Reihenfolge matchen.
    UND den leeren String! ( Null mal die Zeichen....)
    Was wir brauchen, ist ein Ausdruck, der Zahlen erkennt.
    Wir wissen ja nicht, ob es nur 3 Bytes oder 6983 Bytes sind.
    Eine Möglichkeit wäre doch [0-9]*. Das gibt uns schließlich eine Zahl egal welcher Größe.

    Code
    user@host;~>sed -n  '/IpExt: [0-9]*/p' /proc/net/netstat
    IpExt: InNoRoutes InTruncatedPkts InMcastPkts OutMcastPkts InBcastPkts OutBcastPkts InOctets OutOctets InMcastOctets OutMcastOctets InBcastOctets OutBcastOctets
    IpExt: 0 0 237 243 0 0 4032765636 121488405 27043 27283 0 0

    OH! Wieso kommt die erste Zeile immer noch?
    Nun ja, 0 oder beliebig oft.... Und das trifft doch auch auf die erste Zeile zu.
    Merke: der Stern matcht meist viel zuviel und macht uns das Leben schwer. Wir wollen ihn vermeiden.
    Wir können mit {n,m} genau angeben, wie oft der direkt davorstehende Ausdruck vorkommen soll.
    {3} der Ausdruck muss genau dreimal hintereinander enthalten sein.
    {3,} der Ausdruck muss mindestens dreimal (bis zu unendlich oft) hintereinander enthalten sein.
    {,4} der Ausdruck darf fehlen, oder maximal viermal hintereinander vorkommen.


    Und dann gibt es noch benannte Zeichenklassen, die uns das Leben sehr viel leichter machen. Sie werden mit [:name:] geschrieben. name kann sein:
    alpha für jeden Buchstaben
    alnum für jeden Buchstaben oder Ziffer
    digit für eine Zahl
    xdigit für eine Hexadezimale Zahl ( 0 bis 9 plus aAbB bis fF )
    (es gibt noch ein paar mehr....)


    Diese Zeichenklassennamen haben noch einen Vorteil: Sie werden nach der eingestellten LOCALE sortiert!
    Wir brauchen uns also bei der Verwendung von Zeichenklassennamen nicht mehr um die korrekte Sortierreihenfolge kümmern.


    [[:digit:]] bezeichnet also genau ein Zeichen ( []), das der Zeichenklasse [:digit:] angehört.

    Für uns ist also der Ausdruck ^IpExt: [[:digit:]]{1,} vielversprechend.
    Er matcht Zeilen, die am Anfang ein IpExt: stehen haben, gefolgt von einem Leerzeichen und dann von einer Zahl mit einer oder mehreren Ziffern.


    Für sed müssen wir nur die geschweiften Klammer maskieren. Den Ausdruck also mit je einem Backslash vor den geschweiften Kalmmern schreiben: ^IpExt: [[:digit:]]\{1,\}

    Code
    user@host: > sed -n '/^IpExt: [[:digit:]]\{1,\}/p' /proc/net/netstat
    IpExt: 0 0 240 246 0 0 4037283317 122226062 27277 27517 0 0

    Und wir sind wieder ein Stück weiter auf unserem Weg.


    Was wir jetzt noch brauchen, ist das gezielte Herausgreifen unserer Zahl aus der Zeile.


    Alle RegExe können auch Gruppieren. Das geschieht mit runden Klammern. Steht etwas in Klammern, so kann man später auf den Klammerinhalt mit \n zugreifen. n steht dabei für eine Zahl von 1 bis 9 und bezeichnet die Klammerpaare in der Reihenfolge, in der sie im Ausdruck stehen.
    Zwei Beispiele:
    s/(ich) bin (doof)/\1 war \2/p Dieser Ausdruck sucht nach einer Zeile, die die Buchstabenfolge ich bin doof enthält. Hat es diese Zeichenfolge gefunden, werden sie ersetzt ( s/ausdruck/ersatz/) zuerst durch das Klammerpaar Nummer 1 (also "ich") , dann durch "war" und dann durch das Klammerpaar Nummer 2 ( also "doof"). Und das Resultat wird schließlich ausgegeben: ich war doof


    s/(ich) bin (doof)/\2 war \1/p gibt demzufolge einfach doof war ich aus.


    Wir brauchen also letztlich für unseren Ausdruck nur noch die Zahl im entsprechenden Feld zu Gruppieren und dann auszugeben.
    Vorher müssen wir noch die ersten Zahlen überlesen. Auch das erledigen wir mit einer Gruppierung. Nur verwenden wir sie ein klein wenig anders diesmal.
    /([[:digit:]]{1,} ){6}/ Dieser Ausdruck sucht eine Zahl mit 1 oder mehr Stellen.gefolgt von einem Leerzeichen. Und das wird gruppiert, so dass der nachfolgende Anzahl-Ausdruck {6} sich auf die ganze Klammer also die Zahl mit beliebig vielen Stellen SAMT Leerzeichen bezieht.
    Damit treffen wir in der gesuchten Zeile die ersten 6 Zahlen (egal, wieviele Stellen) und ein nachfolgendes Leerzeichen.


    Zum Verständnis schreibe ich den Ausdruck jetzt mit vielen Leerzeichen als Code. Nicht das, was wir wirklich wollen, aber lesbar.


    Wollen wir also die siebte Zahl mit beliebig vielen Stellen aus einer Zeile lesen, schreiben wir einfach:

    Code
    s/  (  zahl{1,}   ){6}  ( zahl{1,} / \2


    Wir ersetzen also die Zeile, in der eine Zahl (mit beliebig vielen Stellen) UND einem Leerzeichen (vor dem ){6} steht ein Leerzeichen) sechsmal hintereinander vorkommt UND danach wieder eine beliebig große Zahl mit der beliebig großen Zahl.

    Code
    s/ (  [[:digit]]{1,}  ){6}  ( [[:digit:]]{1,} )  /   \2   /


    Zahl.

    Code
    s/  (  [[:digit]]{1,} ){6}     ([[:digit:]]{1,})  / \2    /


    Wie oben schon gesagt, müssen wir bei sed die geschweiften Klammern mit einem Backslash maskieren.
    Dasselbe gilt auch für die runden Klammern. Unser Ausdruck lautet jetzt also:

    Code
    s/   \(  [[:digit]] \{1,\}  \) \{6 \}    \(  [[:digit:]]  \{1, \}    \)     /      \2     /p


    Jetzt sind wir ziemlich am Ziel.
    Alles, was wir jetzt noch brauchen, ist vorneweg unser altbekanntes ^IpExt: (Leerzeichen nicht vergessen!) einzufügen, und die "falschen", der Klarheit geschuldeten, Leerzeichen rauszulöschen:

    Code
    sed -n 's/^IpExt: \([[:digit:]]\{1,\} \)\{6\}\([[:digit:]]\{1,\} \).*/\2/p' /proc/net/netstat

    4 Mal editiert, zuletzt von uhelp ()

    Für den Inhalt des Beitrages 55071 haftet ausdrücklich der jeweilige Autor: uhelp