Anzeige:
Ergebnis 1 bis 1 von 1

Thema: RegEx Tutorial Teil1 mit Beispielen in bash, awk, sed und grep

  1. #1
    Banned
    Registriert seit
    Feb 2005
    Beiträge
    1.151

    RegEx Tutorial Teil1 mit Beispielen in bash, awk, sed und grep

    Einleitung
    Ein RegularExpression ist im wesentlichen ein Filter, den man auf Strings anwenden kann.
    "Strings" sind hier Zeichenketten beliebigen Inhalts; also auch Non-printable-characters, Whitespaces, Tabulatoren usw. mit eingeschlossen.

    Sehr viele Programmiersprachen stellen solche RegEx-engines zur Verfügung. Die einen mächtiger mit vielen Erweiterungen, andere nur geringfügig. Alle haben ihre Eigenheiten.

    Die Programmiersprache perl macht von RegExes regen Gebrauch und hatte als eine der ersten Sprachen eine sehr mächtige RegEx-Engine eingebaut. Noch heute ist PCRE (== PerlCompatibleRegularExpression ein gängiges Akronym. In jeder Sprache, die PCRE kennt, kann man dieses mächtige Werkzeug, so man es einmal gelernt hat, also sofort einsetzen.
    Was für die allemeisten Zwecke ausreicht.

    Die GNU-Systemprogramme awk, sed und grep und die bash selbst verwenden ebenfalls PCRE. Und sogar in meinem Lieblingseditor vi ( oder besser vim) kann man damit zum Beispiel auch Suchen&Ersetzen. Oder mit less irgendetwas Suchen. Selbst man und info beherrschen schlichte RegExes. (Ja, selbst da muss man nicht öde scrollen, sondern kann mittels Suche springen.)

    In diesem Minitutorial stelle ich die Basics vor und gebe Beispiele mit Lösungen für alle vier Programme.
    Natürlich wird man später für den jeweiligen Zweck das "richtige" Tool verwenden, wenn die zu erledigenden Aufgaben komplexer werden.

    einfache RegExes
    Wir verwenden oft schon reguläre Ausdrücke (==RegEx) ohne es wirklich zu merken.
    Code:
    #Schon ein einfaches
    ls z*
    
    # ist letztlich eine RegEx-Suche nach Dateien, deren Namen mit "z" beginnt
    # und dem dann beliebige Buchstaben folgen und davon beliebig viele.
    
    # Auch "greppen" wir oft Zeilen aus einer Datei oder aus der Ausgabe eines Befehles
    grep Dummy  irgendeineDatei
    Hier lassen wir grep nach einer Zeile in der Datei irgendeineDatei suchen, die den RegEx Dummy enthält.
    grep ist übrigens ein Akronym für globalregularexpressionprint
    (Manche sagen get statt global)
    Dummy ist also ein "wortwörtlicher RegEx", oder mathematisch formuliert: Jedes Zeichen ist ein RegEx, der nur auf sich selbst "matched". (Sofern das Zeichen keine Sonderbedeutung hat; dann wäre es mit einem davorstehenden Backslash erst zur normalen Bedeutung zu escapen.)
    Merke
    Das Globbing der bash ist eine Sonderform der RegExes
    (Globbing nennt man das Expandieren von *?[] zu Dateinamen, was die bash beim parsen der eingegebenen Kommandozeile macht.)

    Wie man RegExes liest
    Der RegEx Dummy ist also ein Filter, der im Wesentlichen sagt: Ein großes "D", gefolgt von einem kleinen "u", gefolgt von einem "m", gefolgt von einem "m", gefolgt von einem "y". Wir schreiben gefolgt von einem "m" zweimal hintereinander, nicht gefolgt von 2 "m".
    Die RegEx-Engine liest (bei Sprachen mit LeftToRight Leserichtung) den RegEx von links nach rechts und hört auf, sobald ein vollständiger Match erreicht wurde.
    Wir werden später auf diesen Punkt noch ausführlicher zurückkommen.
    (Und klar ist die Leserichtung umgekehrt im Hebräischen oder Arabischen Sprachen)

    Mengenangaben
    Das gibt es natürlich auch: ein m{2} sagt genau zwei "m".
    m{4,7} meint genau vier bis sieben "m".
    m{,8} meint null bis 8 "m". Und noch m{3,} meint letztlich mehr als 3 "m"
    (Korrekt wäre übrigens m{0,8}, aber viele RegExDialekte erlauben das Auslassen.)

    Diesen Mengenangaben ist eines allen gemein:
    Sie beziehen sich auf das direkt VORHERGEHENDE Was-auch-immer:
    Der RegEx abcd{2} matched abcdd, aber niemals abcdabcd
    ("match" ist der "Fachbegriff" für zutreffen)

    Und natürlich gibt es auch die Mengenangaben eins, zwei, viele.
    Ein a* meint Null bis unendlich viele as.
    a+ meint beliebig viele as, aber mindestens eines.

    Dem aufmerksamen Leser sollte nun schon die Frage auf der Zunge liegen, dass das ja dem ls z* widerspricht.
    Richtig. Wenn wir das Globbing der bash verwenden, meint der * beliebig viele beliebige Zeichen und nicht, wie bei einem RegEx das von Null bis unendlich oft vorkommende Zeichen vor dem Stern.

    Die Mengenoperatoren ?*+{} heißen in der Fachsprache "Quantifikatoren".

    Joker für beliebige Zeichen
    Ein Regex der dem Globbing der bash entspricht, wäre .*
    Der . meint ein beliebiges Zeichen, und der * ermöglicht die beliebige Wiederholung und die Auslassung (Null mal vorkommend) des beliebigen Zeichens.
    Wollen wir nach einem Punkt selbst suche, so müssen wir ihn durch das Voranstellung eines Backslashes escapen, wie üblich unter den Unices.

    Das optionale Vorkommen (oder in anderen Worten: das Null oder einmalige Vorkommen) lässt sich mit ? in einem RegEx ausdrücken. Abweichend vom bash Globbing, wo es ein einziges beliebiges Zeichen meint.
    a? meint also, dass an dieser Stelle ein a stehen kann, oder auch nicht.

    Zeichenklassen
    Oft will man nach einer Menge von Zeichen suchen, die an einer Stelle stehen. Wollen wir z.B. alle Strings finden, die mit bild beginnen, dem ein A oder ein B folgt, so schreiben wir als RegEx einfach bild[AB]
    [AB] meint hier genau ein Zeichen aus der Menge, die die Zeichen A und B enthält, also genau ein A oder genau ein B. Wir wissen schon, dass wir beliebig Quantifikatoren anhängen können.
    Natürlich muss man die Menge von a bis f nicht [abcdef] schreiben, sondern kann sie mit dem nur innerhalb einer Zeichenklasse gültigen Bereichsoperator - abkürzen zu [a-f]
    [a-f] meint also genau ein a oder b oder .... oder f
    Das ganze geht natürlich auch mit Zahlen.
    [0-4] meint entweder eine 0 oder eine 1 oder ... oder eine 4.
    Merke
    Zahlen haben in RegExes KEINERLEI numerische Bedeutung.
    Sie sind lediglich Zeichen, die nach der LOCALE sortiert werden können.
    Wir reden immer nur von Zeichen, nie von Zahlen.

    Zeichenklassen können auch negiert werden.
    [^A] meint JEDES Zeichen außer A
    Eine Klasse wie [^23] meint JEDES Zeichen außer dem Zeichen 2 oder dem Zeichen 3
    Das kann also ein a oder jedes andere Zeichen sein, nur eben keine 2 und keine 3
    Diese Negierung muss aber direkt nach der öffnenden Klammer stehen. Steht es nicht am Anfang der Zeichenklasse, so ist es lediglich das Zeichen ^ selbst.
    [A^B] meint also entweder ein A oder ein B oder ein ^. Keine Verneinung. Das steht kein nicht B.

    Damit können wir jetzt einen RegEx für hexadezimale Zahlen so schreiben:
    [a-zA-Z0-9] eine hexadezimale Ziffer
    [a-zA-Z0-9]{3} eine 3-stellige hexadezimale Ziffer
    [^a-zA-Z0-9] alles außer einer hexadezimale Ziffer
    [^a-zA-Z0-9]{3} drei Zeichen die kein Zeichen der hexadezimalen Ziffern enthalten

    Und natürlich lässt sich auch das [a-zA-Z0-9] abkürzen zu[[:xdigit:]]

    Eine Liste dieser "Abkürzungen":
    [: alpha :] nur Buchstaben
    [:alnum:] alle Buchstaben und Ziffern
    [:blank:] alle Leerzeichen (also auch Tabulatoren )
    [:cntrl:] alle Kontrolzeichen z.B.
    [:digit:] alle Ziffern
    [:graph:] graphische Sonderzeichen
    [:lower:] Kleinbuchstaben
    [:print:] Drucksteuerzeichen
    [:punct:] Interpunktionszeichen, also Punkt, Komma usw.
    [:space:] Leerzeichen (ohne Tabulatoren )
    [:upper:] Großbuchstaben
    [:xdigit:] hexadezimales Zeichen


    Auch diese Abkürzungen lassen sich mit ^ verneinen.
    [^[:digit:]] bezeichnet alles, außer einer Ziffer.
    Beachtet, dass [:digit:] der Name einer Menge ist. Die äußeren [] bezeichnen eine Zeichenklasse.
    Da diese "Klassennamen" aber nur innerhalb einer Zeichenklasse Sinn machen, treten sie halt auch nur innerhalb einer Zeichenklasse auf. Die Negation ^ muss also nach der ersten [ stehen!

    Diese Abkürzungen haben noch einen Vorteil. Wir müssen uns nicht darum kümmern, ob in Japan ein Komma ein Satztrenner ist. All solche Eigenschaften werden automatisch berücksichtigt. Und alle Zeichen werden der jeweiligen Landessprache (besser der LOCALE nach) sortiert. Bevor es sie gab, musste man für jedes andere Land selbst mühsam Zeichenklassen definieren.

    Anfang, Ende und Anker
    Wenn das ^ nicht innerhalb von [] steht, ist es keine Negation eines Zeichens, sondern meint den Anfang des Strings.
    ^[^a] sagt also, dass am Anfang des Strings/der Zeile kein a stehen darf.

    Und das $ Zeichen meint immer das Ende eines Strings/einer Zeile.
    Ein ^BEGIN.*END$ meint also einen String, der am Anfang die Zeichenfolge BEGINhat.
    Dann dürften beliebig viele beliebige Zeichen folgen, aber der String muss auf END enden.

    Die Zeichen ^$ verankern also unseren RegEx am Anfang und Ende eines Strings.
    Dieses Konzept ist sehr wichtig. Wenn die RegExes komplizierter werden, sind sie unerlässlich.
    Wir werden darauf noch häufiger zu sprechen kommen.

    Ein paar praktische Beispiele
    Der Befehl date kennt über 40 Formatdescriptoren. Die kann man sich nicht merken. (Also ich jedenfalls nicht). Ich habe mir dafür einen schlichten Alias definiert:
    Code:
    alias datef="man date | grep -E '^[[:blank:]]+%'"
    Mit alias mycommand="command parameters" definiere ich einen Alias.
    Das Kommando selbst ruft die Manpage von date auf und leitet die Ausgabe mit der Pipe | zum Befehl grep bei dem ich mit -Extended RegExes einschalte und lasse dann nach dem RegEx ^[[:blank:]]+% suchen.
    Der RegEx liest sich: An Anfang der Zeile ^ muss ein Zeichen [] der Zeichenklasse [:blank:] mindestens einmal + stehen und dann muss ein % Zeichen kommen.
    Genau die Beschreibungen aller gültigen date Formatzeichen.

    Mit awk wird das zu
    Code:
    man date | awk ' /^[[:blank:]]+%/ {print $0}'
    Und mit sed zu
    Code:
    man date | sed -rn '/^[[:blank:]]+%/p'
    Alles ziemlich gleich. Die Besonderheiten von sed und awk werde ich in den nächsten Teilen beschreiben.
    Aber man sieht klar, dass die eigentlichen RegExes doch ziemlich gleich sind.

    Und das Ganze lässt sich auch innerhalb von man selbst machen.
    Gebt dazu einfach man 1 date ein (die 1 gibt den Abschnitt der Man- Kataloge direkt mit an und vermeided so die Abfrage, welches Man-Abschnitt man lesen will; man p date würde die POSIX Manpage zu date direkt anzeigen).
    Damit landet ihr innerhalb der Manpage.

    Tippt nun einfach / (das Kommando für "Suche") und unseren RegEx dahinter, gefolgt von Enter.
    /^[[:blank:]]+% landet also ebenfalls unter Verwendung von RegExes bei den Formatbezeichnern.

    Es sind aber schon ein paar Gemeinsamkeiten zu erkennen. Die RegExe sind alle gleich.
    Und die Suche beginnt immer mit / und endet oft auf /

    Häufig will man Dateien und Verzeichnisse mit Datumsangaben verwalten. Wir machen das mal schnell.
    Erstellt euch ein Verzeichnis und wechselt dorthinein. Und dann folgt dem hier:
    Code:
    # wir erstellen uns eine Verzeichnisstruktur
    # dazu verwenden wir die "Brace-Expansion" der bash
    mkdir -p ./bilder{A..B}/bilder_20{13..15}/monat_{01..12}
    
    #damit haben wir Verzeichnisse wie z.B.
    # ./bilderA/bilder_2013/monat_11
    # und erzeugen dort ein paar Dateien 
    find -type d | grep monat | while read dir; do 
        touch $dir/bild_{00..23}Uhr_{00..59}.jpg
    done
    
    # und lassen uns nun spezifische Bilder anzeigen
    ls ./bilder[AB]/bilder_201[35]/monat_04/bild_18Uhr_3[4-9]*
    
    # zum Schluss löschen wir den Käse wieder
    rm -rf bilder*
    Die bash kennt beim Globbing nur ganz einfach RegExes. Die {} sind dort keine Quantoren, sondern reine Bereichsangaben. Diese Brace-Expansion ist nicht auf Zahlen beschränkt sind, es geht auch echo {a..d}.

    Damit endet der erste Teil. Im zweiten wird es spezifischer und damit wirklich nützlicher.
    Zum Üben versucht folgende Dinge:
    Fischt aus der Ausgabe von man man die Zeilen heraus, die beschreiben, welcher Manpage Abschnitt/Katalog für welche Themen Hilfe gibt.
    Eine paar Zeilen davon lauten:
    Code:
           0   Dateiheader (gewöhnlich in /usr/include)
           1   Ausführbare Programme oder Shell-Befehle
           2   Systemaufrufe (Kernel-Funktionen)
    Fehler, Kritik, Vorschläge und Hinweise gerne per PM (und den Hinweis, ob ich das hier veröffentlichen darf)
    Ich werde im nächsten Teil die Regexes vertiefen und mich auf sed und awk konzentrieren.
    Ist anderes gewünscht, einfach per PM mitteilen.

    Dieses Minitutorial steht unter der Lizenz CC BY-NC-SA kann also nichtkommerziell weitergegeben werden unter den gleichen Bedingungen bei Nennung meiner Email karl.thomas.schmidt@googelmail.com
    Geändert von BetterWorld (27.11.15 um 22:04 Uhr)

Ähnliche Themen

  1. bash grep script ?
    Von PR-X im Forum Linux Allgemein
    Antworten: 11
    Letzter Beitrag: 07.04.15, 17:42
  2. use grep with regex in PHP skript with exec()
    Von chaser im Forum Linux Allgemein
    Antworten: 3
    Letzter Beitrag: 24.07.14, 16:34
  3. Bash, grep und der Output
    Von nalye im Forum Anwendungen Allgemein, Software
    Antworten: 6
    Letzter Beitrag: 12.08.10, 14:02
  4. grep mit Regulären Ausdrücken - regex
    Von wotuzu17 im Forum Anwendungen Allgemein, Software
    Antworten: 7
    Letzter Beitrag: 10.01.10, 21:06
  5. grep bash und $?
    Von Windoofsklicker im Forum Linux Allgemein
    Antworten: 3
    Letzter Beitrag: 29.08.02, 13:45

Lesezeichen

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •