Wenn Sie unter Windows, macOS oder den meisten anderen Betriebssystemen arbeiten, verbringen Sie vermutlich die meiste Zeit damit, Anwendungen wie Webbrowser, Textverarbeitungen, Tabellenkalkulationen und Spiele auszuführen. Eine typische Anwendung ist vollgepackt mit Funktionen: Sie enthält praktisch alles, von dem die Entwickler dachten, dass Sie als Anwender es gebrauchen könnten. Deshalb sind die meisten Anwendungen autark bzw. unabhängig, sie hängen nicht von anderen Anwendungen ab. Manchmal kopieren Sie vielleicht Daten zwischen verschiedenen Anwendungen hin und her, aber im Großen und Ganzen sind diese eigenständig und arbeiten getrennt voneinander.
Die Linux-Kommandozeile ist anders. Anstelle von riesigen Anwendungen mit einer Unmenge an Funktionen bietet Linux Tausende kleiner Befehle mit jeweils nur wenigen Funktionen an. Der Befehl cat zum Beispiel gibt Dateien auf dem Bildschirm aus, und das war’s dann auch schon. ls listet die Dateien in einem Verzeichnis auf, mv benennt Dateien um und so weiter. Jeder Befehl hat einen einfachen, ziemlich eindeutig definierten Zweck.
Was ist, wenn Sie etwas Komplizierteres erledigen müssen? Keine Sorge. Linux erlaubt Ihnen, ganz einfach Befehle zu kombinieren, sodass ihre einzelnen Eigenschaften zusammenspielen, damit Sie Ihr Ziel erreichen können. Diese Art des Arbeitens führt zu einer ganz anderen Denkweise. Anstatt zu fragen: »Welche Anwendung sollte ich starten?«, um ein bestimmtes Ergebnis zu erzielen, stellen Sie die Frage: »Welche Befehle sollte ich miteinander kombinieren?«
In diesem Kapitel lernen Sie, wie Sie Befehle in unterschiedlichen Kombinationen anordnen und ausführen, damit sie das machen, was Sie wollen. Um es nicht zu sehr zu verkomplizieren, stelle ich Ihnen nur sechs Linux-Befehle und ihre grundlegendsten Anwendungen vor, damit Sie sich auf den komplexeren und interessanteren Teil konzentrieren können – das Kombinieren dieser Befehle –, ohne erst noch aufwendig etwas anderes lernen zu müssen. Es ist ein bisschen so, als würden Sie mit nur sechs Zutaten das Kochen oder lediglich mit Hammer und Säge das Tischlern erlernen. (In Kapitel 5 packe ich weitere Befehle in Ihren Linux-Werkzeugkasten.)
Sie kombinieren Befehle mithilfe von Pipes, einer Linux-Funktionalität, die die Ausgabe eines Befehls mit der Eingabe eines anderen verbindet. Wenn ich die einzelnen Befehle vorstelle (wc, head, cut, grep, sort und uniq), demonstriere ich auch gleich ihre Verwendung mit Pipes. Manche Beispiele sind ganz praktisch für den täglichen Einsatz mit Linux, während andere lediglich Beispiele darstellen, um eine wichtige Eigenschaft zu verdeutlichen.
Die meisten Linux-Befehle lesen die Eingabe von der Tastatur, schreiben die Ausgabe auf den Bildschirm oder beides. Linux benutzt spezielle Namen für dieses Lesen und Schreiben:
stdin (die »Standardeingabe«)
Der Eingabestrom, den Linux von Ihrer Tastatur einliest. Wenn Sie am Prompt irgendeinen Befehl eintippen, liefern Sie Daten an die Standardeingabe.
stdout (die »Standardausgabe«)
Der Ausgabestrom, den Linux auf Ihren Bildschirm schreibt. Wenn Sie den Befehl ls zum Anzeigen von Dateinamen ausführen, erscheinen die Ergebnisse auf der Standardausgabe.
Jetzt kommt die coole Stelle. Sie können die Standardausgabe eines Befehls mit der Standardeingabe eines anderen Befehls verbinden, sodass der erste Befehl den zweiten speist. Beginnen wir mit dem vertrauten Befehl ls -l zum Auflisten eines großen Verzeichnisses, wie etwa /bin, im Langformat:
$ ls -l /bin
total 12104
-rwxr-xr-x 1 root root 1113504 Jun 6 2019 bash
-rwxr-xr-x 1 root root 170456 Sep 21 2019 bsd-csh
-rwxr-xr-x 1 root root 34888 Jul 4 2019 bunzip2
-rwxr-xr-x 1 root root 2062296 Sep 18 2020 busybox
-rwxr-xr-x 1 root root 34888 Jul 4 2019 bzcat
...
-rwxr-xr-x 1 root root 5047 Apr 27 2017 znew
Dieses Verzeichnis enthält viel mehr Dateien, als Ihr Bildschirm Zeilen anzeigen kann. Daher scrollt die Ausgabe schnell über die Anzeige und verschwindet dann. Schade, dass ls die Informationen nicht bildschirmweise anzeigt, pausiert, bis Sie eine Taste drücken, und dann weitermacht. Aber halt: Ein anderer Linux-Befehl hat genau diese Funktion. Der Befehl less zeigt eine Datei Bildschirm für Bildschirm an:
$ less myfile
Sie können diese beiden Befehle miteinander verbinden, da ls auf die Standardausgabe schreibt und less von der Standardeingabe lesen kann. Verwenden Sie eine Pipe, um die Ausgabe von ls an die Eingabe von less zu senden:
$ ls -l /bin | less
Dieser zusammengesetzte Befehl zeigt den Inhalt des Verzeichnisses bildschirmweise an. Der senkrechte Strich (|) zwischen den Befehlen ist das Linux-Pipe-Symbol.1 Es verbindet die Standardausgabe des ersten mit der Standardeingabe des nächsten Befehls. Jede Kommandozeile, die Pipes enthält, wird als Pipeline bezeichnet.
Den Befehlen ist im Allgemeinen nicht bewusst, dass sie Teil einer Pipeline sind. ls glaubt, dass es auf den Bildschirm schreibt, obwohl seine Ausgabe an less umgeleitet wird. Und less glaubt, dass es von der Tastatur liest, obwohl es tatsächlich die Ausgabe von ls einliest.
Was ist ein Befehl?
Das Wort Befehl besitzt in Linux drei unterschiedliche Bedeutungen, wie Abbildung 1-1 zeigt:
Ein Programm
Ein ausführbares Programm, das mit einem einzigen Wort benannt ist und über dieses Wort ausgeführt wird, wie ls, oder ein ähnliches Merkmal, das in die Shell eingebaut ist, wie cd (ein Shell-Befehl oder Shell-Builtin).2
Ein einfacher Befehl
Ein Programmname (oder Shell-Befehl), dem optional noch Argumente folgen, wie ls -l /bin.
Ein zusammengesetzter Befehl
Mehrere einfache Befehle, die als Einheit behandelt werden, wie etwa die Pipeline ls -l /bin | less.
Abbildung 1-1: Programme, einfache Befehle und zusammengesetzte Befehle werden alle als »Befehle« bezeichnet.
In diesem Buch verwende ich das Wort Befehl auf all diese Arten. Normalerweise ergibt sich aus dem Kontext, wie ich es meine. Falls nicht, werde ich die spezielleren Begriffe verwenden.
Pipes sind ein wesentlicher Teil des Linux-Know-hows. Beginnen wir das Entwickeln Ihrer Pipeline-Fähigkeiten mit einer kleinen Gruppe von Linux-Befehlen, damit Sie gerüstet sind, egal welche Befehle später auf Sie zukommen.
Die sechs Befehle – wc, head, cut, grep, sort und uniq – besitzen zahlreiche Optionen und Betriebsarten, die ich für den Augenblick einmal überspringe, damit wir uns voll und ganz auf die Pipes konzentrieren können. Wenn Sie mehr über die einzelnen Befehle erfahren wollen, rufen Sie den Befehl man auf. Dieser zeigt die jeweils komplette Dokumentation an, zum Beispiel:
$ man wc
Um die sechs Befehle in Aktion zu demonstrieren, verwende ich eine Datei namens animals.txt, die Informationen zu einigen O’Reilly-Büchern enthält und in Beispiel 1-1 zu sehen ist.
Beispiel 1-1: In der Datei animals.txt
python Programming Python 2010 Lutz, Mark
snail SSH, The Secure Shell 2005 Barrett, Daniel
alpaca Intermediate Perl 2012 Schwartz, Randal
robin MySQL High Availability 2014 Bell, Charles
horse Linux in a Nutshell 2009 Siever, Ellen
donkey Cisco IOS in a Nutshell 2005 Boney, James
oryx Writing Word Macros 1999 Roman, Steven
Jede Zeile enthält vier Fakten über ein O’Reilly-Buch, die jeweils durch ein einzelnes Tabulatorzeichen (Tab-Zeichen) getrennt sind: das Tier auf dem vorderen Cover, den Buchtitel, das Jahr der Veröffentlichung und den Namen des ersten Autors.
Der Befehl wc gibt die Anzahl der Zeilen, Wörter und Zeichen in einer Datei aus:
$ wc animals.txt
7 51 325 animals.txt
wc berichtet, dass die Datei animals.txt 7 Zeilen, 51 Wörter und 325 Zeichen enthält. Wenn Sie die Zeichen »von Hand« nachzählen, werden Sie einschließlich der Leerzeichen und Tabs nur 318 Zeichen vorfinden, aber wc bezieht auch das unsichtbare Newline-Zeichen ein, das am Ende jeder Zeile steht.
Die Optionen -l, -w und -c weisen wc an, nur die Anzahl der Zeilen, Wörter bzw. Zeichen auszugeben:
$ wc -l animals.txt
7 animals.txt
$ wc -w animals.txt
51 animals.txt
325 animals.txt
Das Zählen ist eine solch nützliche, vielseitig einsetzbare Aufgabe, dass die Autoren von wc den Befehl so gestaltet haben, dass er mit Pipes funktioniert. Er liest von der Standardeingabe, wenn Sie den Dateinamen weglassen, und schreibt auf die Standardausgabe. Verwenden wir ls, um den Inhalt des aktuellen Verzeichnisses aufzulisten und ihn über eine Pipe an wc weiterzuleiten, damit dieser die Zeilen zählt. Diese Pipeline beantwortet die Frage: »Wie viele Dateien sind in meinem aktuellen Verzeichnis sichtbar?«
$ ls -1
animals.txt
myfile
myfile2
test.py
$ ls -1 | wc -l
4
Die Option -1, die ls anweist, seine Ergebnisse in einer einzigen Spalte auszugeben, ist hier nicht unbedingt nötig. Um zu erfahren, weshalb ich sie verwendet habe, lesen Sie den Kasten »ls ändert sein Verhalten, wenn es umgeleitet wird«.
wc ist der erste Befehl, den Sie in diesem Kapitel gesehen haben. Sie können daher mit Pipes eigentlich noch nicht viel anstellen. Leiten Sie zum Spaß einmal die Ausgabe von wc mit einer Pipe auf sich selbst um. Damit sehen Sie, dass ein Befehl mehr als einmal in einer Pipeline vorkommen kann. Dieser zusammengesetzte Befehl gibt an, dass wc vier Wörter ausgegeben hat: drei Integer und einen Dateinamen:
$ wc animals.txt
7 51 325 animals.txt
$ wc animals.txt | wc -w
4
Warum sollten Sie jetzt anhalten? Fügen Sie der Pipeline einen dritten wc-Befehl hinzu und zählen Sie die Zeilen, Wörter und Zeichen in der Ausgabe »4«:
$ wc animals.txt | wc -w | wc
1 1 2
Die Ausgabe zeigt eine Zeile (die die Zahl 4 enthält), ein Wort (die Zahl 4 selbst) und zwei Zeichen an. Wieso zwei? Weil die Zeile »4« mit einem unsichtbaren Newline-Zeichen endet.
Das sind jetzt genug alberne Pipelines mit wc. Wenn Sie mehr Befehle kennengelernt haben, zeigt sich auch, wie praktisch Pipelines tatsächlich sind.
ls ändert sein Verhalten, wenn es umgeleitet wird
Im Gegensatz zu nahezu jedem anderen Linux-Befehl ist sich ls darüber bewusst, wenn die Standardausgabe der Bildschirm ist oder ob es umgeleitet wurde (in eine Pipe oder anderswohin). Der Grund dafür ist Benutzerfreundlichkeit. Wenn der Bildschirm die Standardausgabe ist, ordnet ls seine Ausgabe in mehreren Spalten an, die sich besser lesen lassen:
$ ls /bin
bash dir kmod networkctl red tar
bsd-csh dmesg less nisdomainname rm tempfile
...
Wird die Standardausgabe dagegen umgeleitet, erzeugt ls nur eine einzige Spalte. Ich demonstriere das, indem ich die Ausgabe von ls mit einer Pipe an einen Befehl leite, der einfach seine Eingabe reproduziert, wie etwa cat:3
$ ls /bin | cat
bash
bsd-csh
bunzip2
busybox
...
Dieses Verhalten kann zu eigenartig aussehenden Ergebnissen führen, wie das folgende Beispiel zeigt:
$ ls
animals.txt myfile myfile2 test.py
$ ls | wc -l
4
Der erste ls-Befehl schreibt alle Dateinamen auf eine Zeile, der zweite Befehl jedoch berichtet, dass ls vier Zeilen erzeugt hat. Falls Ihnen dieses eigenwillige Verhalten von ls nicht bewusst ist, finden Sie diese Diskrepanz möglicherweise verwirrend.
ls besitzt Optionen, um sein Standardverhalten außer Kraft zu setzen. Zwingen Sie ls, eine einzige Spalte auszugeben, indem Sie die Option -1 verwenden, oder erzwingen Sie mehrere Spalten, indem Sie die Option -C einsetzen.
Der Befehl head gibt die ersten Zeilen einer Datei aus. Geben Sie die ersten drei Zeilen von animals.txt aus und nutzen Sie dazu head mit der Option -n:
$ head -n3 animals.txt
python Programming Python 2010 Lutz, Mark
snail SSH, The Secure Shell 2005 Barrett, Daniel
alpaca Intermediate Perl 2012 Schwartz, Randal
Falls Sie mehr Zeilen anfordern, als in der Datei enthalten sind, gibt head die gesamte Datei aus (wie cat). Lassen Sie die Option -n weg, liefert head standardmäßig zehn Zeilen (-n10).
Für sich allein genommen, ist head ganz praktisch, um einen Blick auf den Anfang einer Datei zu werfen, wenn einen der restliche Inhalt nicht interessiert. Der Befehl ist schnell und effizient, selbst bei sehr großen Dateien, weil er nicht die ganze Datei lesen muss. Darüber hinaus schreibt head auf die Standardausgabe, sodass es sich gut für den Einsatz in Pipelines eignet. Zählen Sie die Wörter in den ersten drei Zeilen von animals.txt:
$ head -n3 animals.txt | wc -w
20
Für noch mehr Pipeline-Spaß kann head auch von der Standardeingabe lesen. Eine gebräuchliche Anwendung besteht darin, die Ausgabe eines anderen Befehls zu reduzieren, wenn Sie nicht alles davon sehen wollen, etwa bei einer langen Verzeichnisauflistung. Listen Sie zum Beispiel die ersten fünf Dateinamen aus dem Verzeichnis /bin auf:
$ ls /bin | head -n5
bash
bsd-csh
bunzip2
busybox
bzcat
Der cut-Befehl gibt eine oder mehrere Spalten aus einer Datei aus. Geben Sie zum Beispiel alle Buchtitel aus animals.txt aus; diese stehen in der zweiten Spalte:
$ cut -f2 animals.txt
Programming Python
SSH, The Secure Shell
Intermediate Perl
MySQL High Availability
Linux in a Nutshell
Cisco IOS in a Nutshell
Writing Word Macros
cut bietet zwei Möglichkeiten, um zu definieren, was eine »Spalte« ist. Die erste besteht darin, anhand des Felds (-f) zu schneiden, wenn die Eingabe aus Strings (Feldern) besteht, die jeweils durch ein einzelnes Tab-Zeichen getrennt sind. Praktischerweise ist dies genau das Format der Datei animals.txt. Der gerade gezeigte cut-Befehl gibt dank der Option -f2 das zweite Feld jeder Zeile aus.
Um die Ausgabe zu kürzen, leiten Sie sie mit einer Pipe an head, sodass nur die ersten drei Zeilen ausgegeben werden:
Sie können auch mehrere Felder herausschneiden, indem Sie ihre Feldnummern entweder mit Kommata trennen:
$ cut -f1,3 animals.txt | head -n3
python 2010
snail 2005
alpaca 2012
oder einen numerischen Bereich angeben:
$ cut -f2-4 animals.txt | head -n3
Programming Python 2010 Lutz, Mark
SSH, The Secure Shell 2005 Barrett, Daniel
Intermediate Perl 2012 Schwartz, Randal
Die zweite Möglichkeit, eine »Spalte« für cut zu definieren, erfolgt anhand der Zeichenposition mittels der Option -c. Geben Sie die ersten drei Zeichen aus jeder Zeile der Datei aus, die Sie entweder mit Kommata (1,2,3) oder als Bereich (1-3) angeben:
$ cut -c1-3 animals.txt
pyt
sna
alp
rob
hor
don
ory
Nachdem Sie nun die grundlegende Funktionalität gesehen haben, versuchen Sie einmal etwas Praktischeres mit cut und Pipes. Stellen Sie sich vor, die Datei animals.txt sei Tausende von Zeilen lang und Sie müssten nur die Nachnamen der Autoren extrahieren. Zuerst isolieren Sie das vierte Feld, den Namen des Autors:
$ cut -f4 animals.txt
Lutz, Mark
Barrett, Daniel
Schwartz, Randal
...
Dann leiten Sie die Ergebnisse mit einer Pipe wieder an cut, verwenden nun aber die Option -d (die für »Delimiter«, also »Trennzeichen« steht), um das Trennzeichen auf ein Komma statt eines Tab zu ändern und damit die Nachnamen der Autoren zu isolieren:
$ cut -f4 animals.txt | cut -d, -f1
Lutz
Barrett
Schwartz
...
Sparen Sie Zeit mit Befehls-History und Editing Tippen Sie viele Befehle immer und immer wieder ein? Drücken Sie stattdessen wiederholt die Taste Pfeil-nach-oben. Damit blättern Sie durch die Befehle, die Sie bereits ausgeführt haben. (Diese Eigenart der Shell nennt sich Befehls-History.) Wenn Sie den gewünschten Befehl erreicht haben, drücken Sie Enter, um ihn sofort auszuführen, oder bearbeiten ihn zuerst, indem Sie den Cursor mithilfe der Tasten Pfeil-nach-rechts bzw. Pfeil-nach-links positionieren und mit der Backspace-Taste Zeichen löschen. (Dies nennt sich Kommandozeilen-Editing.) Ich werde in Kapitel 3 noch viel leistungsfähigere Funktionen für die Befehls-History und das Editing vorstellen. |
grep ist ein unglaublich leistungsstarker Befehl. Für den Augenblick werde ich allerdings die meisten seiner Fähigkeiten ignorieren und nur sagen, dass er Zeilen ausgibt, die zu einem bestimmten String passen. (Genaueres folgt in Kapitel 5.) Zum Beispiel zeigt der nächste Befehl Zeilen aus animals.txt an, die den String Nutshell enthalten:
$ grep Nutshell animals.txt
horse Linux in a Nutshell 2009 Siever, Ellen
donkey Cisco IOS in a Nutshell 2005 Boney, James
Sie können auch Zeilen ausgeben lassen, die nicht einem bestimmten String entsprechen. Dazu setzen Sie die Option-v ein. Wie Sie sehen, fehlen hier die Zeilen, die »Nutshell« enthalten:
$ grep -v Nutshell animals.txt
python Programming Python 2010 Lutz, Mark
snail SSH, The Secure Shell 2005 Barrett, Daniel
alpaca Intermediate Perl 2012 Schwartz, Randal
robin MySQL High Availability 2014 Bell, Charles
oryx Writing Word Macros 1999 Roman, Steven
Im Allgemeinen ist grep ganz nützlich zum Suchen von Text in einer Sammlung aus Dateien. Der folgende Befehl gibt Zeilen aus, die den String Perl enthalten und sich in Dateien befinden, deren Namen auf .txt enden:
$ grep Perl *.txt
animals.txt:alpaca Intermediate Perl 2012 Schwartz, Randal
essay.txt:really love the Perl programming language, which is
essay.txt:languages such as Perl, Python, PHP, and Ruby
In diesem Fall fand grep drei passende Zeilen, nämlich eine in animals.txt und zwei in essay.txt.
grep liest von der Standardeingabe und schreibt auf die Standardausgabe, wodurch es sich hervorragend für Pipelines eignet. Nehmen Sie einmal an, Sie wollten wissen, wie viele Unterverzeichnisse sich in dem großen Verzeichnis /usr/lib befinden.
Diese Antwort kann Ihnen kein einzelner Linux-Befehl liefern, weshalb Sie eine Pipeline konstruieren. Beginnen Sie mit dem Befehl ls -l:
$ ls -l /usr/lib
drwxrwxr-x 12 root root 4096 Mar 1 2020 4kstogram
drwxr-xr-x 3 root root 4096 Nov 30 2020 GraphicsMagick-1.4
drwxr-xr-x 4 root root 4096 Mar 19 2020 NetworkManager
-rw-r--r-- 1 root root 35568 Dec 1 2017 attica_kde.so
-rwxr-xr-x 1 root root 684 May 5 2018 cnf-update-db
...
Sie erkennen, dass ls -l Verzeichnisse mit einem d am Anfang der Zeile kennzeichnet. Verwenden Sie cut, um die erste Spalte zu isolieren, die ein d sein kann, vielleicht aber auch keines ist:
$ ls -l /usr/lib | cut -c1
d
d
d
-
-
...
Dann setzen Sie grep ein, um nur die Zeilen zu behalten, die ein d enthalten:
$ ls -l /usr/lib | cut -c1 | grep d
d
d
d
...
Schließlich zählen Sie die Zeilen mit wc und haben Ihre Antwort, die durch eine Pipeline aus vier Befehlen zustande gekommen ist – /usr/lib enthält 145 Unterverzeichnisse:
$ ls -l /usr/lib | cut -c1 | grep d | wc -l
145
Der Befehl sort sortiert die Zeilen einer Datei in aufsteigender Reihenfolge (das ist die Standardeinstellung):
$ sort animals.txt
alpaca Intermediate Perl 2012 Schwartz, Randal
donkey Cisco IOS in a Nutshell 2005 Boney, James
horse Linux in a Nutshell 2009 Siever, Ellen
oryx Writing Word Macros 1999 Roman, Steven
python Programming Python 2010 Lutz, Mark
robin MySQL High Availability 2014 Bell, Charles
snail SSH, The Secure Shell 2005 Barrett, Daniel
oder absteigender Reihenfolge (mit der Option -r):
snail SSH, The Secure Shell 2005 Barrett, Daniel
robin MySQL High Availability 2014 Bell, Charles
python Programming Python 2010 Lutz, Mark
oryx Writing Word Macros 1999 Roman, Steven
horse Linux in a Nutshell 2009 Siever, Ellen
donkey Cisco IOS in a Nutshell 2005 Boney, James
alpaca Intermediate Perl 2012 Schwartz, Randal
sort kann Zeilen alphabetisch (Standardeinstellung) oder numerisch (mit der Option -n) sortieren. Ich zeige dies hier mit Pipelines, die das dritte Feld aus animals.txt herausschneiden, das Erscheinungsjahr:
$ cut -f3 animals.txt Unsortiert
2010
2005
2012
2014
2009
2005
1999
$ cut -f3 animals.txt | sort -n Aufsteigend
1999
2005
2005
2009
2010
2012
2014
$ cut -f3 animals.txt | sort -nr Absteigend
2014
2012
2010
2009
2005
2005
1999
Um das Veröffentlichungsjahr des neuesten Buchs aus animals.txt zu erfahren, leiten Sie die Ausgabe von sort mit einer Pipe auf die Eingabe von head um und geben nur die erste Zeile aus:
$ cut -f3 animals.txt | sort -nr | head -n1
2014
Maximal- und Minimalwerte sort und head sind starke Partner, wenn sie mit numerischen Werten arbeiten, und zwar einem Wert pro Zeile. Sie können den Maximalwert ausgeben, indem Sie die Daten so durch eine Pipe leiten: ... | sort -nr | head -n1 Den Minimalwert geben Sie entsprechend so aus: ... | sort -n | head -n1 |
Spielen wir als weiteres Beispiel mit der Datei /etc/passwd herum, in der die Benutzer stehen, die Prozesse auf Ihrem System ausführen dürfen.4 Sie werden eine Liste aller Benutzer in alphabetischer Reihenfolge generieren. Wenn Sie sich die ersten fünf Zeilen anschauen, sehen Sie etwas in dieser Art:
$ head -n5 /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
smith:x:1000:1000:Aisha Smith,,,:/home/smith:/bin/bash
jones:x:1001:1001:Bilbo Jones,,,:/home/jones:/bin/bash
Jede Zeile besteht aus Strings, die durch Doppelpunkte getrennt sind. Der erste String ist der Benutzername. Sie können also die Benutzernamen mit dem Befehl cut isolieren…
$ head -n5 /etc/passwd | cut -d: -f1
root
daemon
bin
smith
jones
und sortieren:
$ head -n5 /etc/passwd | cut -d: -f1 | sort
bin
daemon
jones
root
smith
Um die sortierte Liste aller Benutzernamen herzustellen, nicht nur der ersten fünf, ersetzen Sie head durch cat:
$ cat /etc/passwd | cut -d: -f1 | sort
Wenn Sie feststellen wollen, ob ein bestimmter Benutzer einen Account auf Ihrem System hat, suchen Sie dessen Benutzernamen mit grep. Eine leere Ausgabe bedeutet, dass es keinen Account gibt:
$ cut -d: -f1 /etc/passwd | grep -w jones
jones
$ cut -d: -f1 /etc/passwd | grep -w rutabaga (erzeugt keine Ausgabe)
Die Option -w weist grep an, nur nach kompletten Wörtern zu suchen, nicht nach Teilwörtern. Dies ist sinnvoll, falls es auf Ihrem System auch Benutzernamen gibt, die »jones« enthalten, wie etwa sallyjones2.
Der Befehl uniq erkennt sich wiederholende benachbarte Zeilen in einer Datei. Die Wiederholungen werden standardmäßig entfernt. Ich demonstriere das anhand einer einfachen Datei, die Großbuchstaben enthält:
$ cat letters
A
A
A
B
B
A
C
C
C
C
$ uniq letters
A
B
A
C
Sie sehen, dass uniq die ersten drei A-Zeilen auf ein einziges A reduziert, das letzte A dagegen blieb bestehen, da es zu den ersten drei A nicht benachbart war.
Sie können auch die Vorkommen zählen. Dazu verwenden Sie die Option -c:
$ uniq -c letters
3 A
2 B
1 A
4 C
Ich gebe zu, dass ich keinen besonderen Nutzen in dem Befehl uniq gesehen hatte, als er mir das erste Mal begegnet war. Aber er ist schnell zu einem meiner Favoriten geworden. Nehmen Sie an, Sie hätten eine Tab-separierte Datei mit den studentischen Abschlussnoten eines Universitätskurses, die von A (am besten) bis F (am schlechtesten) reichen:
$ cat grades
C Geraldine
B Carmine
A Kayla
A Sophia
B Haresh
C Liam
B Elijah
B Emma
A Olivia
D Noah
F Ava
Sie wollen nun die Note ausgeben, die am häufigsten vorkommt. (Bei Gleichstand geben Sie nur einen der Gewinner aus.) Zunächst isolieren Sie die Noten mit cut und sortieren sie:
$ cut -f1 grades | sort
A
A
A
B
B
B
B
C
C
D
F
Anschließend verwenden Sie uniq, um benachbarte Zeilen zu zählen:
$ cut -f1 grades | sort | uniq -c
3 A
4 B
2 C
1 D
1 F
Dann sortieren Sie die Zeilen in umgekehrter Reihenfolge numerisch, um die am häufigsten auftretende Note in die oberste Zeile zu verschieben…
$ cut -f1 grades | sort | uniq -c | sort -nr
4 B
3 A
2 C
1 F
1 D
und nur die erste Zeile mit head zu behalten:
$ cut -f1 grades | sort | uniq -c | sort -nr | head -n1
4 B
Und da Sie nur die Buchstabenbewertung, nicht jedoch die Anzahl haben wollen, isolieren Sie die Note schließlich mit cut:
$ cut -f1 grades | sort | uniq -c | sort -nr | head -n1 | cut -c9
B
Hier haben Sie Ihre Antwort dank einer Pipeline aus sechs Befehlen – unserer längsten bisher. Diese Art der schrittweisen Konstruktion einer Pipeline ist nicht nur eine Fingerübung. So arbeiten Linux-Experten tatsächlich. Kapitel 8 widmet sich dieser Technik.
Kombinieren wir das bisher Gelernte in einem größeren Beispiel. Nehmen wir an, Sie wären in einem Verzeichnis voller JPEG-Dateien unterwegs und wollten wissen, ob einige von ihnen Duplikate sind:
$ ls
image001.jpg image005.jpg image009.jpg image013.jpg image017.jpg
image002.jpg image006.jpg image010.jpg image014.jpg image018.jpg
...
Sie können diese Frage mit einer Pipeline beantworten. Sie brauchen einen weiteren Befehl, nämlich md5sum, der den Inhalt einer Datei untersucht und einen 32 Zeichen langen String berechnet, eine sogenannte Prüfsumme oder Checksumme:
$ md5sum image001.jpg
146b163929b6533f02e91bdf21cb9563 image001.jpg
Die Prüfsumme einer Datei ist aus mathematischen Gründen mit sehr, sehr hoher Wahrscheinlichkeit einmalig. Wenn zwei Dateien dieselbe Checksumme besitzen, handelt es sich daher ziemlich sicher um Duplikate. Hier zeigt md5sum an, dass die erste und die dritte Datei Duplikate sind:
$ md5sum image001.jpg image002.jpg image003.jpg
146b163929b6533f02e91bdf21cb9563 image001.jpg
63da88b3ddde0843c94269638dfa6958 image002.jpg
146b163929b6533f02e91bdf21cb9563 image003.jpg
Prüfsummen von Duplikaten sind relativ leicht durch bloßes Hinschauen zu erkennen, wenn man nur drei Dateien hat. Was machen Sie aber bei 3.000 Dateien? Hier sind Pipes unsere Rettung. Berechnen Sie alle Prüfsummen, isolieren Sie mit cut die ersten 32 Zeichen auf jeder Zeile und sortieren Sie die Zeilen, damit die Duplikate nebeneinanderstehen:
$ md5sum *.jpg | cut -c1-32 | sort
1258012d57050ef6005739d0e6f6a257
146b163929b6533f02e91bdf21cb9563
146b163929b6533f02e91bdf21cb9563
17f339ed03733f402f74cf386209aeb3
...
Fügen Sie dann uniq hinzu, um die sich wiederholenden Zeilen zu zählen:
$ md5sum *.jpg | cut -c1-32 | sort | uniq -c
1 1258012d57050ef6005739d0e6f6a257
2 146b163929b6533f02e91bdf21cb9563
1 17f339ed03733f402f74cf386209aeb3
...
Wenn es keine Duplikate gibt, sind die von uniq erzeugten Zahlen alle 1. Sortieren Sie die Ergebnisse numerisch vom höchsten zum niedrigsten Wert; alle Zahlen, die größer sind als 1, erscheinen am Anfang der Ausgabe:
$ md5sum *.jpg | cut -c1-32 | sort | uniq -c | sort -nr
3 f6464ed766daca87ba407aede21c8fcc
2 c7978522c58425f6af3f095ef1de1cd5
2 146b163929b6533f02e91bdf21cb9563
1 d8ad913044a51408ec1ed8a204ea9502
...
Nun entfernen wir noch die Nichtduplikate. Vor deren Prüfsummen stehen sechs Leerzeichen, die Zahl 1 und ein einzelnes Leerzeichen. Verwenden wir grep -v, um diese Zeilen zu entfernen:5
$ md5sum *.jpg | cut -c1-32 | sort | uniq -c | sort -nr | grep -v " 1 "
3 f6464ed766daca87ba407aede21c8fcc
2 c7978522c58425f6af3f095ef1de1cd5
2 146b163929b6533f02e91bdf21cb9563
Schließlich haben Sie Ihre Liste aller Duplikatprüfsummen, sortiert nach der Anzahl der Vorkommen und erzeugt durch eine wunderbare Pipeline aus sechs Befehlen. Wenn diese keine Ausgabe erzeugt, gibt es keine doppelt vorkommenden Dateien.
Dieser Befehl wäre noch nützlicher, wenn er die Dateinamen der Duplikate anzeigen würde. Allerdings erfordert diese Operation Funktionen, die wir bisher nicht besprochen haben. (Sie lernen sie in »Den Duplikate-Detektor verbessern« auf Seite 109 kennen.) Für den Augenblick identifizieren wir die Dateien, die eine bestimmte Prüfsumme haben, über eine Suche mit grep:
$ md5sum *.jpg | grep 146b163929b6533f02e91bdf21cb9563
146b163929b6533f02e91bdf21cb9563 image001.jpg
146b163929b6533f02e91bdf21cb9563 image003.jpg
und säubern die Ausgabe mit cut:
$ md5sum *.jpg | grep 146b163929b6533f02e91bdf21cb9563 | cut -c35-
image001.jpg
image003.jpg
Sie haben nun die Stärken von Standardeingabe, Standardausgabe und Pipes kennengelernt. Diese verwandeln eine kleine Handvoll von Befehlen in eine Sammlung kombinierbarer Werkzeuge, die beweisen, dass das Ganze größer ist als die Summe seiner Teile. Jeder Befehl, der von der Standardeingabe liest oder auf die Standardausgabe schreibt, kann in Pipelines eingesetzt werden.6 Wenn Sie mehr Befehle kennengelernt haben, können Sie die allgemeinen Konzepte aus diesem Kapitel nutzen, um Ihre eigenen leistungsfähigen Kombinationen zu bauen.