grep ist eines der ältesten und meistgenutzten Unix-Werkzeuge überhaupt — der Standard, wenn es darum geht, Zeilen zu finden, die ein bestimmtes Muster enthalten. Aus einer Datei, aus einem ganzen Verzeichnisbaum oder mitten aus einer Pipeline. Wer grep beherrscht, durchsucht Codebasen schneller als jede IDE, filtert Logs auf der Konsole und verkettet beliebige Kommandos zu robusten Pipelines. Der Name stammt aus dem ed-Editor: g/re/p — global / regular expression / print.

Was grep macht

grep liest Zeilen aus Dateien oder von der Standardeingabe und gibt jene aus, die ein angegebenes Pattern enthalten. Im einfachsten Fall ist das Pattern ein literaler String, im allgemeinen Fall ein regulärer Ausdruck. Per Default geht jeder Treffer als komplette Zeile auf die Standardausgabe — alles andere lässt sich über Optionen steuern.

grep ist vermutlich das meistgenutzte Filter-Tool der Unix-Welt. Es taucht in Tutorials, Skripten und Live-Debugging-Sessions gleichermassen auf. Der Grund: Die Aufgabe „zeig mir die Zeilen, in denen X vorkommt” ist allgegenwärtig — in Logs, in Konfigurationsdateien, in Quellcode, in der Ausgabe anderer Kommandos.

Bash Klassische Suche in einer Datei
grep ERROR /var/log/syslog
Bash Aus einer Pipeline filtern
ps aux | grep nginx

Wichtigste Optionen

grep hat viele Schalter, aber nur eine Handvoll davon braucht man im Alltag wirklich oft. Diese Tabelle deckt die wichtigsten ab — nach Häufigkeit gewichtet.

OptionWirkung
-iCase-insensitive Match
-vNegation: Zeilen ausgeben, die nicht matchen
-nZeilennummer mit ausgeben
-cNur die Anzahl der Treffer pro Datei
-lNur Dateinamen, die mindestens einen Treffer haben
-LNur Dateinamen ohne Treffer
-r / -RRekursiv durchsuchen (-R folgt Symlinks)
-wNur ganze Wörter matchen (Wortgrenzen)
-xNur ganze Zeilen matchen
-EExtended Regex (ERE) statt BRE
-FFeste Strings, kein Regex
-PPerl-kompatible Regex (PCRE)
-oNur den matchenden Teil ausgeben, nicht die ganze Zeile
-A NN Zeilen nach dem Treffer mitausgeben
-B NN Zeilen vor dem Treffer mitausgeben
-C NN Zeilen um den Treffer (Kontext)
-qQuiet — kein Output, nur Exit-Code
--include='GLOB'Nur passende Dateinamen durchsuchen
--exclude='GLOB'Passende Dateinamen überspringen
--exclude-dir=DIRVerzeichnisse vom rekursiven Abstieg ausschließen
-h / -HDateinamen-Header unterdrücken / erzwingen
-zNUL-getrennte Records statt Newlines
--color=autoTreffer farbig hervorheben

-q ist besonders nützlich in Skripten: grep -q PATTERN file && echo "gefunden" prüft auf Existenz, ohne irgendeinen Output zu produzieren. Der Exit-Code von grep ist 0 bei mindestens einem Treffer, 1 ohne Treffer und 2 bei einem echten Fehler — perfekt für if-Bedingungen.

Regex-Varianten: BRE, ERE, PCRE

grep versteht drei verschiedene Regex-Dialekte. Welcher gerade gilt, hängt von der Option ab — und das ist eine der häufigsten Fehlerquellen.

DialektAktivierungBesonderheiten
BRE (Basic)Default, ohne Schalter+, ?, {}, (), `
ERE (Extended)-E oder egrep+, ?, (), `
PCRE (Perl)-PLookahead/Lookbehind, non-greedy *?, \d, \b

Mit BRE schreibt man grep 'foo\+' für „foo gefolgt von einem oder mehr Zeichen”, mit ERE einfach grep -E 'foo+'. Aus diesem Grund nehmen viele Leute heute fast immer -E — die Syntax ist näher an dem, was man aus anderen Regex-Engines kennt. PCRE mit -P ist nochmal mächtiger (Lookbehind, non-greedy, Named Groups), aber nicht überall verfügbar — auf BSD-Systemen wie macOS fehlt der Schalter im Standard-grep.

Anker und Klassen funktionieren in allen Dialekten gleich:

KonstruktBedeutung
^Zeilenanfang
$Zeilenende
\bWortgrenze (PCRE; in BRE/ERE meist \< und \>)
[a-z]Zeichenklasse
[^abc]Negierte Klasse
[[:digit:]]POSIX-Klasse für Ziffern
[[:alpha:]]POSIX-Klasse für Buchstaben
[[:space:]]POSIX-Klasse für Whitespace

POSIX-Klassen sind locale-unabhängig und meist die beste Wahl für portable Skripte. [a-z] matcht je nach LC_COLLATE mal nur ASCII, mal auch Umlaute — [[:lower:]] ist eindeutiger.

Bash ERE: alternative Endungen
grep -E '\.(log|tmp|bak)$' files.txt
Bash PCRE: Lookbehind
grep -P '(?<=user=)\w+' access.log

Mehrere Pattern

Soll grep nach mehreren Mustern gleichzeitig suchen, gibt es drei Wege. Jeder hat seinen Platz.

Bash Mehrere -e Optionen
grep -e ERROR -e WARN -e FATAL syslog
Bash Pattern aus Datei
grep -f patterns.txt access.log
Bash Alternation in ERE
grep -E 'ERROR|WARN|FATAL' syslog

In BRE schreibt sich die Alternation \| (mit Backslash), in ERE direkt |. Wer eine wirklich große Liste von Patterns hat — etwa zehntausende IPs aus einer Sperrliste — sollte grep -F -f liste.txt nehmen: Mit -F (fixed strings) ist die Suche um Größenordnungen schneller, weil keine Regex-Engine bemüht wird.

Recursive Search mit -r

Die Killer-Anwendung von grep im Entwickleralltag: rekursive Suche durch ganze Codebasen. grep -r 'TODO' src/ findet jeden TODO-Kommentar im Verzeichnisbaum unterhalb von src/.

Bash Alle TODOs im Projekt
grep -rn 'TODO' src/

-n ergänzt Zeilennummern, -r macht es rekursiv. Aber in einem realen Projekt gibt es Müll, den man nicht durchsuchen will: node_modules, .git, dist, build. Hier kommen --include und --exclude-dir ins Spiel.

ComboWirkung
grep -rn 'X' .Alles rekursiv ab .
grep -rn --include='&#42;.ts' 'X' .Nur TypeScript-Dateien
grep -rn --exclude-dir=node_modules 'X' .node_modules überspringen
grep -rn --exclude='&#42;.min.js' 'X' .Minified-Code ausschließen
grep -rln 'X' .Nur Dateinamen mit Treffern
grep -rwn 'foo' .Nur ganzes Wort foo, kein foobar
Bash Alltagsbefehl: TypeScript-Suche ohne Müll
grep -rn --include='*.ts' --exclude-dir=node_modules 'useEffect' src/

--include und --exclude arbeiten mit Glob-Patterns, nicht mit Regex. *.ts ist ein Glob, \.ts$ wäre Regex. Beide Optionen lassen sich beliebig oft angeben.

Context: -A, -B, -C

Einer der größten Grep-Klassiker: Bei einem Treffer auch die umliegenden Zeilen ausgeben. Das ist beim Lesen von Logs Gold wert — der Fehler steht in einer Zeile, der Kontext, warum es krachte, in den drei Zeilen davor.

OptionBedeutung
-A NN Zeilen nach (after) dem Treffer
-B NN Zeilen vor (before) dem Treffer
-C NN Zeilen um den Treffer (context, beidseitig)
Bash Drei Zeilen vor jedem Fehler
grep -B 3 'ERROR' syslog
Bash Fünf Zeilen Kontext um jeden Match
grep -C 5 'connection refused' /var/log/nginx/error.log

Treffer-Blöcke werden mit -- voneinander getrennt, sobald mehrere Matches im selben Output landen. Wer den Separator stört, schaltet ihn mit --no-group-separator ab.

Word-Match -w und Line-Match -x

Mit -w matcht grep nur dann, wenn das Pattern an Wortgrenzen beginnt und endet. grep -w foo findet foo in foo bar, aber nicht in foobar. Das ist genau das, was man bei Variablen- oder Funktionsnamen will.

Bash Nur das ganze Wort 'main'
grep -wn 'main' *.go

-x geht noch weiter und matcht nur dann, wenn die gesamte Zeile dem Pattern entspricht — Teiltreffer in der Mitte der Zeile zählen nicht. Nützlich, wenn du in einer Liste prüfen willst, ob ein exakter Eintrag vorhanden ist.

Bash Exakte Zeile als Eintrag prüfen
grep -Fx 'admin' users.txt

-F zusammen mit -x ist die robusteste Kombination für „ist dieser exakte String als ganze Zeile in der Datei?” — keine Regex-Sonderzeichen werden interpretiert.

grep vs. moderne Alternativen

Inzwischen gibt es eine ganze Reihe schnellerer und entwicklerfreundlicherer Such-Tools. Sie sind im Tagesgeschäft am eigenen Rechner oft die bessere Wahl — aber sie sind eben nicht überall installiert.

ToolStärkenSchwächen
grepÜberall vorinstalliert, POSIX-StandardLangsam bei Codebasen, kein .gitignore
ripgrep (rg)Blitzschnell, .gitignore-aware, schöne DefaultsMuss installiert werden
ag (silver searcher)Schnell, .gitignore-awareVorgänger von rg, weniger gepflegt
ackCode-fokussiert, in Perl geschriebenLangsamer als rg

ripgrep ist heute der De-facto-Standard für Code-Suche am Entwickler-Rechner: schneller als grep, ignoriert per Default .gitignore-Einträge und Binärdateien, hat farbige Ausgabe out of the box. Für interaktives Arbeiten ein klarer Gewinn.

Bash Mit ripgrep: gleicher Effekt, weniger Tippen
rg 'useEffect' --type ts

Aber: Auf einem fremden Server, in einem Container-Image oder im Recovery-Modus gibt es nur grep. Auch in CI-Skripten und portablen Shell-Tools ist grep die sichere Wahl. Deshalb gehört es weiterhin zum Pflichtrepertoire — selbst wenn du im Alltag rg bevorzugst.

Praxis-Patterns

Eine Sammlung der Einzeiler, die im Alltag immer wieder gebraucht werden.

Bash Fehler im Syslog finden
grep -i error /var/log/syslog

-i ignoriert Groß- und Kleinschreibung — nötig, weil Logs mal ERROR, mal Error, mal error schreiben. Ohne -i würden zwei von drei Schreibweisen durchrutschen.

Bash JSON-Feld grob suchen
grep '"id"' response.json

Für saubere JSON-Verarbeitung ist jq natürlich besser — aber für ein schnelles „existiert das Feld überhaupt?” reicht grep allemal.

Bash Leere Zeilen herausfiltern
grep -v '^$' config.txt

^$ ist der reguläre Ausdruck für eine komplett leere Zeile (Anfang direkt gefolgt von Ende), -v invertiert die Auswahl. Wer Zeilen aus Whitespace ebenfalls verwerfen will, nimmt '^[[:space:]]*$'.

Bash Anzahl der Treffer
grep -c 'WARN' app.log

-c zählt Treffer-Zeilen, nicht Treffer — kommt WARN zweimal in einer Zeile vor, wird trotzdem nur eins gezählt. Für eine echte Vorkommens-Zählung lieber grep -o pattern file | wc -l verwenden.

Bash Dateien OHNE Lizenz-Header
grep -L 'SPDX-License-Identifier' *.c

-L ist das Gegenstück zu -l: listet Dateinamen, in denen das Pattern nicht vorkommt. Ideal für Compliance-Checks oder um vergessene Marker (Lizenzheader, Copyright-Notice, Test-IDs) aufzuspüren.

Bash nginx-Prozesse, ohne grep selbst
ps aux | grep -v grep | grep nginx

Klassisches Problem: grep nginx findet sich selbst in der Prozessliste, weil das Wort nginx im eigenen Kommando steht. Der grep -v grep-Filter davor wirft den eigenen Prozess raus. Eleganter ist pgrep nginx — aber wenn nur grep da ist, hilft dieser Trick.

Bash Kommentare und Leerzeilen in Configs ausblenden
grep -Ev '^(#|$)' /etc/ssh/sshd_config

Filtert in einem Schritt sowohl Kommentar- als auch Leerzeilen heraus — übrig bleiben nur die effektiv aktiven Direktiven. Ideal, um in zugemüllten Configs (sshd, postfix, php.ini) den realen Zustand auf einen Blick zu sehen.

Bash IP-Adressen aus einer Datei extrahieren
grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log

-o druckt nur das gematchte Teilstück, nicht die ganze Zeile — perfekt zum Extrahieren. Das Pattern ist bewusst grob: es matcht auch ungültige IPs wie 999.999.999.999. Für strikte Validierung greift man zu awk oder perl, hier reicht es zum schnellen Aufzählen.

Stolperfallen

grep | grep -v grep ist ein Klassiker — pgrep ist besser

Die Pipeline ps aux | grep nginx matcht auch das grep nginx-Kommando selbst. Der Klassiker dagegen ist ps aux | grep -v grep | grep nginx oder der Trick ps aux | grep ‘[n]ginx’ — die Klammer matcht zwar das Zeichen n, aber das Pattern selbst enthält keinen literalen nginx-String. Beides funktioniert, aber für „läuft Prozess X?” ist pgrep nginx die saubere Antwort.

Punkt matcht alles — Punkt im Suchstring escapen

In Regex bedeutet . „ein beliebiges Zeichen”. grep ‘foo.bar’ matcht also foo bar, foo-bar, fooXbar — und eben auch foo.bar. Wer wirklich einen literalen Punkt sucht, schreibt grep ‘foo.bar’ oder benutzt grep -F ‘foo.bar’. Mit -z (NUL-getrennte Records) matcht . sogar Newlines.

Spezialzeichen ohne Quotes — die Shell expandiert

grep $variable file ist gefährlich: Wenn $variable Leerzeichen oder Glob-Zeichen enthält, zerlegt die Shell das Argument oder ersetzt es durch Dateinamen. grep “*.log” . findet Treffer für das Pattern star Punkt log — sofern keine Datei *.log heißt, sonst expandiert die Shell zu Dateinamen, die grep als Pattern frisst. Faustregel: Pattern immer in einfache Quotes setzen.

BRE und ERE verwirren — Plus ohne Backslash matcht literal

In BRE (default) ist + ein literales Plus-Zeichen, kein Quantifier. grep ‘foo+’ sucht also nach dem String foo+, nicht nach „foo gefolgt von einem oder mehr Zeichen”. Wer + als Quantifier will, nimmt -E (ERE) oder schreibt es in BRE als +. Dasselbe gilt für ?, {}, () und |. Im Zweifel -E nehmen.

PCRE -P ist nicht überall verfügbar

Auf macOS und FreeBSD ist grep die BSD-Implementierung — sie hat keinen -P-Schalter. Skripte mit Lookbehind oder non-greedy Patterns brechen dort ab. Lösungen: GNU-grep installieren (brew install grep, dann ggrep -P nutzen), das Pattern in ERE umschreiben, oder auf perl -ne bzw. ripgrep ausweichen. Bei portablen Skripten lieber gleich auf POSIX-ERE bleiben.

Color-Output bricht weitere Pipe-Stages

Mit —color=auto (oder per Alias erzwungen) fügt grep ANSI-Escape-Codes in den Output ein. Beim direkten Lesen am Terminal hübsch — beim Weiterverarbeiten in einer Pipeline desaströs: grep —color=always X | sort sortiert plötzlich nach ANSI-Sequenzen, und grep —color X | grep Y findet Y oft nicht, weil die Bytes mit Farbcodes durchschossen sind. In Skripten immer —color=never verwenden.

grep -r folgt Symlinks per Default nicht — -R schon

Kleines, aber gemeines Detail: -r (Kleinbuchstabe) folgt symbolischen Links nicht, -R (Großbuchstabe) tut es. Wer ein Verzeichnis untersucht, in dem ein Symlink auf das Elternverzeichnis zeigt, wird mit -R in eine Endlosschleife laufen. Im Zweifel -r nehmen — es ist die sicherere Default-Wahl. Wenn Symlinks bewusst gefolgt werden sollen, -R.

--include und --exclude sind Globs, kein Regex

Sehr häufiger Fehler: grep -r —include=’.ts$’ X . findet nichts, weil —include Glob-Patterns erwartet. Korrekt ist —include=‘*.ts’. Auch ohne Quotes droht Ärger — die Shell expandiert *.ts sonst zu den Dateinamen im aktuellen Verzeichnis, bevor grep sie überhaupt sieht. Pattern in Quotes, immer.

Locale beeinflusst [a-z] — Umlaute können mitmatchen

Die Klasse [a-z] ist nicht so portabel, wie sie aussieht. Je nach LC_COLLATE kann sie auch Umlaute, Akzente oder gar Großbuchstaben mitmatchen — die Sortierreihenfolge bestimmt, was zwischen a und z liegt. Robust ist die POSIX-Klasse [[:lower:]] oder das explizite Forcen der C-Locale: LC_ALL=C grep ‘[a-z]’ file. Skripte, die plattformübergreifend laufen müssen, sollten POSIX-Klassen bevorzugen.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Textverarbeitung

Zur Übersicht