Regular Expressions sind Muster mit denen Texte verglichen werden können oder auch Infos aus Texten extrahiert werden können. Zum Beispiel könnt ihr damit überprüfen, ob eine Logdatei von einem Programm die Zeichenfolge “Error” enthält (also um zu prüfen, ob ein Fehler aufgetreten ist). Oder um einfach nur zu schauen, ob eine Zeichenkette einem bestimmten Muster entspricht, zum Beispiel einer E-Mail Adresse.
🎬 Ich habe übrigens auch ein Video zu dem Thema erstellt.
Diese Muster gibt es in vielen Programmiersprachen. Die Details der Implementierungen können sich dann in den Details aber etwas unterscheiden. PowerShell und die RegEx-Engine der PowerShell basieren auf .NET. Deshalb gelten hier vorallem die .NET Eigenheiten.
In der PowerShell gibt es verschiedene Möglichkeiten um Regular Expressions zu verwenden - also zum Beispiel in Cmdlets, als Vergleichsoperator oder per .NET Methode. Im Folgenden zeige ich euch ein paar dieser Möglichkeiten. Außerdem zeige ich euch, wie ihr die Vergleichsmuster also die tatsächlichen Regular Expressions erstellen könnt.
Der -match Operator
Eine einfache Möglichkeit um einen Vergleich per Regular Expressions zu machen, ist der -match
Vergleichsoperator. Zum Beispiel so:
|
|
Hier wird geprüft, ob im Text “Hallo PowerShell” auch die Zeichenkette “shell” enthalten ist. Das Ergebnis ist True
, also wahr. Und hierbei sehen wir jetzt schon die erste Besonderheit: Denn in der PowerShell sind Regular Expressions standardmäßig nicht case-sensitive - also die Groß- und Kleinschreibung wird ignoriert. Ihr könnt aber auch explizit angeben, dass ihr Case-Sensitive vergleichen wollt, oder ganz explizit nicht. Explizit anzugeben, dass nicht case-sensitive gearbeitet wird, obwohl es ja eigentlich dem Standard spricht - das klingt vielleicht erstmal komisch. Hat aber seine Daseinsberechtigung. Wenn ihr das nämlich macht, dann wird spiegelt euer Code ein bisschen genauer wider, was ihr eigentlich machen wollt.
|
|
Es gibt auch alle drei match
Operatoren auch noch in umgedreht, nämlich immer noch mit dem Zusatz “not” (also Englisch für “nicht”):
|
|
Platzhalter und Quantifier
Aber mit Regulären Ausdrücken können wir auch mehr: Also nicht einfach nur ein Wort oder ein Stück Text als Vergleichsmuster verwenden. Es gibt verschiedene Zeichen, mit einer speziellen Bedeutung. Zum Beispiel ein Punkt .
steht für ein beliebiges Zeichen. Und man kann auch mit einem zusätzlichen Zeichen die Anzahl des gewünschten Zeichens angeben, auf Englisch wird das “quantifier” genannt. So einen “quantifier” hängt man dran, also es bezieht sich immer auf die Sache davor. Ein Quantifier ist zum Beispiel das Stern-Symbol *
was für eine beliebige Anzahl steht, also auch inklusive 0. Das +
Symbol steht für die Anzahl 1 oder mehr. Das ?
steht für die Anzahl 0 oder 1.
Also kann ich zum Beispiel auch so einen Vergleich machen:
|
|
Statt also hier explizit das “ä” anzugeben, habe ich den Punkt als Platzhalter verwendet. Das ergibt auch True
.
Passen wir das nochmal an und nehmen einen “quantifier” mit rein.
|
|
Der Stern steht ja für eine beliebige Anzahl. Also könnte ich auch den String anpassen, sodass dort “mächtig” mit drei “ä” steht.
|
|
Oder es würden auch andere Zeichen gematched werden.
|
|
Ob das letzte Beispiel ein ordentlicher deutscher Satz ist, lassen wir mal dahin gestellt. Aber auch diese Vergleichsoperation ergibt True
, weil da irgendwo ein m
steht und dann später chtig
.
Übrigens: Den Quantifier können wir auch bei normalen Zeichen verwenden, also nicht nur bei Spezialzeichen wie dem Punkt. Um zum Beispiel stattdessen zu prüfen, ob 0, 1 oder mehr ä
enthalten sind könnten wir folgendes machen:
|
|
Hinweis: Wenn ihr eines von den Spezialzeichen []().\^$|?*+{}
matchen wollt, also wenn ihr zum Beispiel schauen wollt, ob das Stern-Symbol oder ein Punkt in einem Text drin steht, dann funktioniert das erstmal nicht. Um gegen so ein Spezialzeichen vergleichen zu können, müssen wir das Zeichen “escapen”. Dafür schreiben wir ein Backslash-Symbol \
vor das Zeichen, welches wir escapen wollen. Zum Beispiel so:
|
|
Das gilt übrigens auch für das Backslash-Symbol. Also um zu prüfen ob ein Backslash Symbol enthalten ist, müssen wir es mit einem Backslash escapen, sodass dann dort zwei Backslashes im Vergleichsmuster stehen.
|
|
Mehr Infos zu den anderen Spezialzeichen und Platzhaltern kommen im Laufe des Artikels.
Die $Matches Variable
Bei der Verwendung der Match-Operatoren passiert noch mehr als nur die Rückgabe von True
oder False
. Wenn das Ergebnis des Vergleichs True
ist, dann wird die Variable $Matches
dem ersten Suchtreffer im Text befüllt. Das macht eigentlich erst Sinn, wenn man mit irgendwelchen Platzhaltern oder Quantifiern arbeitet.
Machen wir mal noch einen Vergleich mit dem match
-Operator und schauen in die Variable rein.
Mein Text in dem ich etwas prüfen möchte, habe ich grob an HTML oder XML Code angelehnt.
Und mein Vergleichsmuster schaut nach den Tags mit dem Namen <test> </test>
und dazwischen dürfen dann 0 oder mehr beliebige Zeichen stehen.
Da das Ergebnis True
ist, wird die $Matches
Variable automatisch befüllt.
|
|
Capture Groups
Mit Capture Groups können wir bestimmte Bereiche aus dem Text extrahieren. Auch mehrere Capture Groups in einer regular Expression sind möglich, aber wir fangen erstmal mit einer an.
Bleiben wir mal bei dem Beispiel vom vorherigen Abschnitt. Wenn ich jetzt genau den Text extrahieren möchte, der sich innerhalb der <test>
-Tags befindet, dann ist der bisherige Code noch nicht optimal. Aber das Verwenden von Capture Groups ist eigentlich ganz einfach, wir müssen einfach den Bereich den wir als Capture Group behandeln wollen in Runde Klammern ()
stecken. Das Ergebnis von Capture Groups landet ebenfalls in der $Matches
Variable.
|
|
Die $Matches
Variable ist übrigens eine Hashtable. Der Eintrag mit dem Namen “0” gibt unseren gesamten gematchten String zurück. Und wenn wir eine Capture Group verwenden, dann landet die im Eintrag “1”. Wenn wir noch mehr Capture Groups verwenden würden, dann könnte es auch noch mehr Einträge geben, dann würde einfach weiter hochnummeriert werden.
Zum Beispiel extrahiere ich hier drei Infos per Capture Groups:
|
|
Jetzt habe ich Einträge von 0-3 in meiner $Matches
Variable. Wenn ihr die im Code weiter verarbeiten wollt, könnt ihr wie bei jeder anderen Hashtable auch, auf die einzelnen Einträge zugreifen. Zum Beispiel so:
|
|
Hier ein Bild um visuell zu zeigen, wo was gecaptured wird:
Allerdings wird der Code durch diese Nummerierungen schnell unübersichtlich. Um den Code besser verständlich zu machen, können wir “Named Captures” verwenden. Also Namen für unsere Capture Groups festlegen. Dafür müssen wir an den Anfang der Capture Group, aber innerhalb der runden Klammern, erstmal ein ?
schreiben und dann zwischen einem kleiner-als-Zeichen <
und einem Größer-als-Zeichen >
den gewünschten Namen. Der Eintrag in der $Matches
-Hashtable erhält dann statt einer Nummer den gewünschten Namen.
|
|
Ein kleines Manko gibt es bei der Verwendung von Capture Groups mit dem -match
Operator: Wenn ihr mehrfach matchen wollt, dann geht das nicht. Also wenn eure Regular Expression mehrfach zutrifft auf euren String, dann wird nur das erste Ergebnis zurückgeliefert. Zum Beispiel so:
|
|
Der String den ich hier überprüft habe, enthält zwei Blöcke mit diesem <test>
-Tag. Wenn ich jetzt in die $Matches
Variable reinschaue, dann ist da aber trotzdem nur ein Treffer drin. Das lässt sich auch nicht anpassen bei der Verwendung des -match
Operators. Wenn ihr mehrere Treffer matchen wollt und dabei vielleicht auch noch Capture Groups verwenden wollt, dann müsst ihr eine andere Methode verwenden (zum Beispiel die .NET Klasse RegEx
).
Greedy Captures
Euch ist vielleicht beim letzten Beispiel im vorherigen Abschnitt (Capture Groups) aufgefallen, dass meine Regular Expression noch ein Fragezeichen ?
enthält. Durch dieses Fragezeichen habe ich eingestellt, dass die RegEx Engine hier nicht wie im Standard “greedy” ist - (zu deutsch “gierig”). Normalerweise versucht die Engine im gierigen Modus soviele Zeichen wie möglich mit der Regular Expression zu matchen. Das kann aber zu unerwünschten Ergebnissen führen. Im Vergleich hier einmal das Ergebnis ohne das Fragezeichen.
|
|
Besonderheiten und Tipps zum match Operator
Eine Besonderheit beim -match
Operator: Wenn ihr mehrere Objekte gleichzeitig als Eingabe nehmt, also zum Beispiel so:
|
|
Dann kriegt ihr nicht True
oder False
zurück, sondern jeweils alle Strings bei denen die Regular Expression erfolgreich gematched hat. In so einem Fall wird die automatische Variable $Matches
auch nicht befüllt. Also wenn da vorher etwas drin stand, dann ist noch der alte Wert drin. Und wenn zuvor noch nichts drin stand, dann steht da jetzt auch nichts drin.
Meine Praxistipps für den Match Operator sind wie folgt:
Wenn ihr nur prüfen wollt, ob ein Muster zutreffend ist, gibt es nicht viel zu beachten. Macht einfach eine
If
-Abfrage. Das funktioniert sowohl bei einem Objekt als Input, als auch mit mehreren.1 2 3 4 5
$MeineVariable = "Kanal abonnieren" if($MeineVariable -match "abo") { # Das hier wird ausgeführt, wenn die Regular Expression True ergibt "... und Video liken!" }
Ansonsten solltet ihr sicherstellen, dass ihr wisst wieviele Objekte ihr an
-match
übergebt. Es ist ein ganz klassischer Fehler in der Programmlogik, dass nicht beachtet wird, dass ein Cmdlet auch mal mehr Objekte zurücklieferen kann als nur eins. Um sicherzustellen, dass nur ein Objekt geprüft wird, könnt ihr zum Beispiel mitSelect-Object -First 1
die Rückgabe auf ein Objekt beschränken.1 2 3 4 5
$MeineVariable = "[email protected]", "[email protected]" | Select-Object -First 1 if($MeineVariable -match "user") { "User gefunden!" # irgendwas mit dem User machen }
Nur wenn ihr wirklich nur gegen ein Objekt matched, könnt ihr dem Inhalt der Variable
$Matches
vertrauen.Wenn ihr eine Regular Expression habt, die mehrfach auf euren String passt, und ihr alle Treffer wissen wollt, und vielleicht sogar mit Capture Groups die Infos rausziehen wollt, dann ist der
-match
Operator dafür nicht geeignet. Es gibt aber andere Möglichkeiten zum Beispiel die .NET RegEx Klasse.
Der -replace Operator
Mit dem -replace
Operator wird der Text der dem RegEx Muster entspricht durch einen anderen Text ersetzt.
Zunächst ein einfaches Beispiel, bei dem einfach das Zeichen W
inklusive allem was danach folgt durch das Wort PowerShell
ersetzt wird.
|
|
Aber der -replace
Operator unterstützt auch Capture Groups. Und dann können wir die Capture Groups in unserem Ergebnis verwenden.
|
|
Die $Matches
Variable wird übrigens nicht befüllt, wenn wir den -replace
Operator verwenden. Falls sie bei euch noch gesetzt ist, dann ist das noch von einem vorherigen Vergleich per -match
Operator.
Der -split Operator
Mit dem -split
Operator könnt ihr Strings aufteilen. Und auch hier können nicht nur einfache Zeichenfolgen als Trennzeichen verwendet werden, sondern auch Regular Expressions.
Einfaches Beispiel:
|
|
Und ein komplexeres Beispiel:
|
|
Der -split
Operator unterstützt noch ein paar weitere Optionen. Ich finde es aber nicht so interessant und habe keine coolen Beispiele. Schaut dafür am besten in die Dokumentation bei Microsoft: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_split?view=powershell-7.4
Select-String Cmdlet
Mit dem Cmdlet Select-String
kann ein String oder ein Dateiinhalt mit einer Regular Expression verglichen werden. Oder auch mehrere Strings oder Dateiinhalte.
Zum Beispiel vergleiche ich hier mehrere Objekte, in dem Fall einfache Strings, indem ich sie per Pipeline an Select-Object
übergebe.
|
|
Von den drei Objekten erhalte ich nur zwei zurück, da nur zwei dem Muster entsprechen. In der PowerShell 7 wird es sogar auch noch in der Konsole hervorgehoben, wo das Muster zutrifft.
Auch bei Select-String
wird die Groß- und Kleinschreibung im Standard erstmal ignoriert. Wenn ihr wollt, dass sie beim Vergleichen beachtet wird, dann könnt ihr den zusätzlichen Parameter -CaseSensitive
mit angeben. Außerdem könnt ihr die Überprüfung auch umdrehen, indem ihr den Parameter -NotMatch
angebt. Dann würden nur die Strings zurückgegeben werden, die nicht dem Muster entsprechen.
Aber diese Sachen könnten wir ja auch schon mit dem -match
Operator erreichen. Was ich dann interessanter finde, ist die Möglichkeit eine oder mehrere Dateien zu prüfen.
Zum Beispiel überprüfe ich so alle Dateien mit der Dateiendung .log
im Verzeichnis C:\diecknet
:
|
|
Wenn wir jetzt eine oder mehrere Textdateien mit dem Cmdlet prüfen, dann kriegen wir bei den Suchtreffern auch noch die Information in welcher Datei sich der Treffer befindet und in welcher Zeile.
Manchmal möchte man aber noch ein bisschen mehr Infos bekommen, ein bisschen Kontext. Dafür gibt es den Parameter -Context
. Da kann ich wahlweise eine Zahl angeben, und soviele Zeilen kriege ich dann darüber und darunter mit ausgegeben.
|
|
Die tatsächliche Zeile mit dem Suchergebnis ist dann mit einem Größer-als-Zeichen >
markiert.
Wir können aber auch getrennt steuern, wieviele Zeilen davor und danach wir erhalten möchten. Dafür einfach zwei Zahlen als Parameterwert angeben und mit einem Komma trennen. In folgendem Beispiel werden eine Zeile davor und 5 danach zurückgegeben.
|
|
Das was uns hier von Select-String
zurückgeliefert wird, ist übrigens auch mehr als einfach nur Text. Tatsächlich kriegen wir hier Objekte vom Typ MatchInfo
zurück.
|
|
Das MatchInfo Objekt hat einige interessante Eigenschaften, auf die wir dann per Code zugreifen können. Wir könnten beispielsweise auch per Code die genaue Zeilennummer eines Datei-Treffers herausfinden.
|
|
Falls ihr euch für mehrere Treffer in einer Zeile interessiert, dann könnt ihr das auch aktivieren mit dem Parameter -AllMatches
.
|
|
Switch Statement mit -RegEx Parameter
Beim Switch
Statement können auch reguläre Ausdrücke verwendet werden. Dafür muss der Parameter -RegEx
mit angegeben werden. Bei jedem Bedingungsblock des Switch Statements kann dann ein regulärer Ausdruck eingetragen werden.
|
|
.NET RegEx Matches
Die mächtigste Möglichkeit um Regular Expressions in der PowerShell zu verwenden, ist meiner Meinung nach die .NET Klasse RegEx
. Es gibt verschiedene Möglichkeiten die Klasse zu benutzen. Die einfachste ist meiner Meinung nach Folgende:
|
|
Wenn die Regular Expression erfolgreich gegen den String gematched wurde, dann erhaltet ihr ein Match Objekt zurück. Daran ist dann unter anderem erkennbar, dass das Muster erkannt wurde. Und ihr könnt per Code auf die Eigenschaften zugreifen. Ich benutze die .NET Variante hier vorallem wenn ich mehrere Infos auf einmal per Capture Groups rausziehen möchte. Also nicht einfach nur mehrere Capture Groups, sondern dass das Muster mehrfach zutrifft. Ich habe hier zum Beispiel eine Text-Datei mit folgendem Inhalt (das ist übrigens alles in einer Zeile):
Mit folgendem PowerShell Code lese ich die Text-Datei zunächst ein und prüfe dann mit der [regex]::Matches()
Methode ob ein Muster darauf zutrifft.
|
|
Der Code extrahiert alle Zeichenfolgen die exakt 32 Zeichen lang sind und ausschließlich aus Hexadezimalen Zeichen bestehen. Dabei hat die Regular Expression \b([a-f0-9]{32})\b
genau eine Capture Group. Da ich die Matches
Methode aus der RegEx
Klasse aufgerufen habe, kriege ich nicht nur den ersten Suchtreffer zurück, sondern alle.
Wichtig: Bei der .NET Klasse RegEx
solltet ihr genau auf die Groß- und Kleinschreibung achten! Im Gegensatz zu den anderen RegEx Möglichkeiten in der PowerShell, ist sie standardmäßig case-sensitive. Der folgende Code würde keinen Treffer im String finden:
|
|
Aber die .NET [RegEx]::Matches()
Methode unterstützt diverse Optionen. Unser bisheriges Beispiel kann um einen dritten Parameter ergänzt werden, um auch hier die Groß- und Kleinschreibung zu ignorieren:
|
|
💡 Weitere Optionen könnt ihr hier in der Microsoft Dokumentation finden: https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-options?redirectedfrom=MSDN
Außerdem gibt es noch weitere Methoden in der RegEx
Klasse, die je nach Anwendungsfall auch noch sehr praktisch sein können. Schaut dafür in die Dokumentation zur RegEx
Klasse: https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex?view=net-8.0#methods
Regular Expressions entwerfen
Okay, kommen wir also dazu wie ihr Regular Expressions entwerfen könnt. Wie zum Beispiel die Expression von eben: \b([a-f0-9]{32})\b
Also ganz ehrlich: Ich finde Regular Expressions sind nicht einfach. Ich habe nicht genug Erfahrung mit Regular Expressions, dass ich die einfach so runtertippe ohne irgendwelche Hilfe. Aber ich guck eigentlich gar nicht erst in die Microsoft Dokumentation zu RegEx in .NET oder PowerShell. Stattdessen nehme ich ein Tool um ein RegEx Muster zu entwerfen. Es gibt einige kostenlose Websites, mit denen man Regular Expressions im Browser bauen kann und direkt live gegen einen oder mehrere Strings testen kann. Ich habe in letzter Zeit gerne RegEx101.com verwendet, weil dort auch speziell mit Bezug auf die .NET Eigenheiten gearbeitet werden kann. In der Vergangenheit habe ich auch andere allgemeinere Websites benutzt, die dann z.B. eher an JavaScript orientiert sind. Aber meistens hat das auch richtig funktioniert. Keine Ahnung wie seriös diese RegEx101.com Seite ist, also ich würde da jetzt keine personenbezogenen Daten oder sonst wie was kritisches reintun.
Wichtig: Auf der Seite müsst ihr links den Flavor auf “.NET” umstellen.
Wenn ihr jetzt ein Muster entwerfen wollt, dann schaut unten rechts in den Bereich “Quick Reference”. Dort werden alle möglichen Platzhalter und Spezialzeichen erklärt. Außerdem gibt es den Bereich Explanation, der die erstellte Regular Expression erklärt. Sehr nice!
Schlusswort
Das waren meiner Meinung nach die wichtigsten Möglichkeiten Regular Expressions in der PowerShell zu verwenden. Vermutlich gibt auch noch mehr Möglichkeiten. Aber das Grundprinzip das war hoffentlich verständlich, sodass ihr Regular Expressions hoffentlich auch an den anderen Stellen anwenden könnt.
Ein wichtiger Hinweis noch: Wenn ihr nur einen einfachen Vergleich durchführen wollt, ist es empfehlenswert Regular Expressions gar nicht erst zu verwenden. Denn die Verwendung der RegEx Engine hat einen gewissen Overhead. Im Zweifelsfall testen und Zeit messen!