Linux-Systeme besitzen Tausende von Kommandozeilenprogrammen. Erfahrene Benutzer verlassen sich normalerweise auf eine kleinere Teilmenge – eine Art Werkzeugkasten –, zu dem sie immer und immer wieder zurückkehren. Kapitel 1 fügte Ihrem Werkzeugkasten sechs ausgesprochen nützliche Befehle hinzu, und ich empfehle Ihnen nun ein weiteres Dutzend (oder mehr). Ich werde jeden Befehl kurz beschreiben und Ihnen einige Beispielanwendungen zeigen. (Wenn Sie alle verfügbaren Optionen kennenlernen wollen, schauen Sie in die Manpage des jeweiligen Befehls.) Ich stelle außerdem zwei leistungsfähige Befehle vor, die schwieriger zu erlernen sind, aber die Mühe wirklich lohnen: awk und sed. Insgesamt dienen die Befehle in diesem Kapitel vier gebräuchlichen, praktischen Zwecken für Pipelines und andere komplexe Befehle:
Text erzeugen
Datumsangaben, Zeiten, Abfolgen von Zahlen und Buchstaben, Dateipfade, wiederholte Strings und anderen Text ausgeben, um Ihre Pipelines auf den Weg zu bringen.
Text isolieren
Irgendeinen Teil einer Textdatei mit einer Kombination aus grep, cut, head, tail und einer praktischen Eigenschaft von awk extrahieren.
Text kombinieren
Dateien von oben nach unten mit cat und tac oder Seite an Seite mit echo und paste kombinieren. Sie können Dateien außerdem mit paste und diff verschachteln.
Text transformieren
Text mit einfachen Befehlen wie tr und rev oder leistungsfähigeren Befehlen wie awk und sed konvertieren.
Dieses Kapitel ist ein schneller Überblick. Spätere Kapitel zeigen die Befehle in Aktion.
Jede Pipeline beginnt mit einem einfachen Befehl, der auf die Standardausgabe schreibt. Manchmal ist es ein Befehl wie grep oder cut, der ausgewählte Daten aus einer Datei zieht:
$ cut -d: -f1 /etc/
passwd | sort Gibt alle Benutzernamen aus und sortiert sie.
oder sogar cat, der ganz praktisch ist, wenn man den kompletten Inhalt mehrerer Dateien mittels Pipelines an andere Befehle leiten möchte:
$ cat *.txt | wc -l Gesamtzeilenzahl
Zu anderen Gelegenheiten kommt der Anfangstext in einer Pipeline aus anderen Quellen. Sie kennen bereits einen solchen: ls gibt Datei- und Verzeichnisnamen und damit verbundene Informationen aus. Schauen wir uns weitere Befehle und Techniken an, die Text erzeugen:
date
Gibt Datums- und Uhrzeitangaben in verschiedenen Formaten aus.
seq
Gibt eine Abfolge von Zahlen aus.
Klammererweiterung
Eine Shell-Eigenschaft, die eine Abfolge von Zahlen oder Zeichen ausgibt.
find
Gibt Dateipfade aus.
yes
Gibt wiederholt die gleiche Zeile aus.
Der Befehl date gibt in verschiedenen Formaten das aktuelle Datum und/oder die Uhrzeit aus:
$ date Standardformatierung
Mon Jun 28 16:57:33 EDT 2021
$ date +%Y-%m-%d Jahr-Monat-Tag-Format
2021-06-28
$ date +%H:%M:%S Stunde:Minute:Sekunden-Format
16:57:33
Um das Ausgabeformat zu steuern, geben Sie ein Argument an, das mit einem Pluszeichen beginnt (+), gefolgt von beliebigem Text. Der Text kann spezielle Ausdrücke enthalten, die mit einem Prozentzeichen (%) beginnen, wie etwa %Y für das aktuelle vierstellige Jahr und %H für die aktuelle Stunde auf einer 24-Stunden-Uhr. Eine komplette Liste der Ausdrücke finden Sie auf der Manpage für date.
$ date +"Ich kann kaum glauben, dass es schon %A ist!" Wochentag
Ich kann kaum glauben, dass es schon Tuesday ist!
Der seq-Befehl gibt eine Abfolge von Zahlen in einem Bereich aus. Geben Sie zwei Argumente an, nämlich den niedrigsten und den höchsten Wert des Bereichs. seq gibt dann den gesamten Bereich aus:
$ seq 1 5 Gibt alle Integer-Werte von 1 bis einschließlich 5 aus.
1
2
3
4
5
Wenn Sie drei Werte angeben, definieren der erste und der dritte Wert den Bereich, während der zweite das Inkrement bestimmt:
$ seq 1 2 10 Inkrementiert um 2 statt um 1.
1
3
5
7
9
Benutzen Sie ein negatives Inkrement wie etwa -1, um eine absteigende Sequenz zu erzeugen:
$ seq 3 -1 0
3
2
1
0
Mit einem Dezimalinkrement erzeugen Sie Gleitkommazahlen:
$ seq 1.1 0.1 2 Inkrementiert um 0,1.
1.1
1.2
1.3
...
2.0
Die Werte werden standardmäßig durch ein Newline-Zeichen getrennt, Sie können das Trennzeichen aber auch mit der Option -s gefolgt von einem String ändern:
$ seq -s/ 1 5 Trennt Werte durch Schrägstriche.
1/2/3/4/5
Die Option -w sorgt dafür, dass alle Werte die gleiche Breite (in Zeichen) haben, indem sie bei Bedarf führende Nullen hinzufügt:
$ seq -w 8 10
08
09
10
seq kann Zahlen auch in vielen anderen Formaten erzeugen (siehe Manpage). Meine Beispiele repräsentieren die gebräuchlichsten Fälle.
Die Shell bietet ihre eigene Methode zum Ausgeben einer Abfolge von Zahlen, die sogenannte Klammererweiterung. Beginnen Sie mit einer öffnenden (linken) geschweiften Klammer, fügen Sie zwei Integer-Werte hinzu, die durch zwei Punkte getrennt sind, und beenden Sie das Ganze mit einer schließenden (rechten) geschweiften Klammer:
$ echo {1..10} vorwärts von 1
1 2 3 4 5 6 7 8 9 10
$ echo {10..1} rückwärts von 10
10 9 8 7 6 5 4 3 2 1
$ echo {01..10} mit führenden Nullen (gleiche Breite)
01 02 03 04 05 06 07 08 09 10
Allgemeiner ausgedrückt: Der Shell-Ausdruck {x..y..z} generiert die Werte x bis y, inkrementiert um z:
$ echo {1..1000..100} Zählt von 1 an um Hunderte.
1 101 201 301 401 501 601 701 801 901
$ echo {1000..1..100} rückwärts von 1000
1000 900 800 700 600 500 400 300 200 100
$ echo {01..1000..100} mit führenden Nullen
0001 0101 0201 0301 0401 0501 0601 0701 0801 0901
Geschweifte Klammern versus eckige Klammern Eckige Klammern sind ein Operator für das Pattern-Matching von Dateinamen (siehe Kapitel 2). Die Erweiterung geschweifter Klammern hat dagegen nichts mit Dateinamen zu tun. Sie wird einfach nur zu einer Liste von Strings ausgewertet. Sie können die Klammererweiterung einsetzen, um Dateinamen auszugeben, es tritt aber kein Pattern-Matching auf: |
|
|
$ ls file1 file2 file4 $ ls file[2-4] Erfasst vorhandene Dateinamen. file2 file4 $ ls file{2..4} Wird ausgewertet zu: file2 file3 file4. ls: cannot access 'file3': No such file or directory file2 file4 |
Die Klammererweiterung kann auch Sequenzen aus Buchstaben erzeugen, was seq nicht kann:
$ echo {A..Z}
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Bei der Klammererweiterung wird die Ausgabe immer auf einer einzigen Zeile erzeugt, die Abtrennung erfolgt durch Leerzeichen. Sie können dies ändern, indem Sie die Ausgabe durch eine Pipeline an andere Befehle leiten, wie etwa tr (siehe »Der tr-Befehl« auf Seite 104):
$ echo {A..Z} | tr -d ' ' Löscht Leerzeichen.
ABCDEFGHIJKLMNOPQRSTUVWXYZ
$ echo {A..Z} | tr ' ' '\n' Ändert Leerzeichen in Newlines.
A
B
C
...
Z
Erzeugen Sie einen Alias, der den n. Buchstaben des englischen Alphabets ausgibt:
$ alias nth="echo {A..Z} | tr -d ' ' | cut -c"
$ nth 10
J
Der find-Befehl listet rekursiv die Dateien in einem Verzeichnis auf, wobei er in die Unterverzeichnisse hinuntersteigt und die kompletten Dateinamen ausgibt.1 Die Ergebnisse sind nicht alphabetisch (schicken Sie die Ausgabe notfalls mit einer Pipe an sort):
$ find /etc -print Listet rekursiv den ganzen Inhalt von /etc auf.
/etc
/etc/issue.net
/etc/nanorc
/etc/apache2
/etc/apache2/sites-available
/etc/apache2/sites-available/default.conf
...
find besitzt zahllose Optionen, die Sie miteinander kombinieren können. Hier sind einige ganz besonders nützliche. Beschränken Sie mit der Option -type die Ausgabe nur auf Dateien oder nur auf Verzeichnisse:
$ find . -type f -print nur Dateien
$ find . -type d -print nur Verzeichnisse
Beschränken Sie die Ausgabe mit der Option -name auf Namen, die einem Dateinamensmuster entsprechen. Schützen Sie das Muster mit Anführungszeichen, damit die Shell es nicht auswertet:
$ find /etc -type f -name "*.conf" -print Dateien, die auf .conf enden.
/etc/logrotate.conf
/etc/systemd/logind.conf
...
Mit der Option -iname verhindern Sie, dass die Groß-/Kleinschreibung von Namen beim Mustervergleich eine Rolle spielt:
$ find . -iname "*.txt" -print
find kann mit der Option -exec auch einen Linux-Befehl für jeden Dateipfad in der Ausgabe ausführen. Die Syntax ist ein bisschen wackelig:
Hier ist ein Beispiel, bei dem ein @-Symbol an beiden Seiten des Dateipfads ausgegeben wird:
$ find /etc -exec echo @ {} @ ";"
@ /etc @
@ /etc/issue.net @
@ /etc/nanorc @
...
Bei diesem praktischeren Beispiel erfolgt eine lange Auflistung (ls -l) aller .conf-Dateien in /etc und seinen Unterverzeichnissen:
$ find /etc -type f -name "*.conf" -exec ls -l {} ";"
-rw-r--r-- 1 root root 703 Aug 21 2017 /etc/logrotate.conf
-rw-r--r-- 1 root root 1022 Apr 20 2018 /etc/systemd/logind.conf
-rw-r--r-- 1 root root 604 Apr 20 2018 /etc/systemd/timesyncd.conf
...
find -exec funktioniert gut, wenn man Massenlöschungen von Dateien in einer Verzeichnishierarchie vornehmen möchte (seien Sie jedoch vorsichtig!). Löschen wir einmal alle Dateien, deren Namen mit einer Tilde (~) enden, im Verzeichnis $HOME/tmp und seinen Unterverzeichnissen.
Aus Sicherheitsgründen führen Sie zuerst den Befehl echo rm aus, damit Sie sehen, welche Dateien gelöscht werden würden. Anschließend entfernen Sie echo, um das Löschen tatsächlich durchzuführen:
$ find $HOME/tmp -type f -name "*~" -exec echo rm {} ";" echo zur Sicherheit
rm /home/smith/tmp/file1~
rm /home/smith/tmp/junk/file2~
rm /home/smith/tmp/vm/vm-8.2.0b/lisp/vm-cus-load.el~
$ find $HOME/tmp -type f -name "*~" -exec rm {} ";" tatsächliches Löschen
Der yes-Befehl gibt denselben String immer und immer wieder aus, bis er (der Befehl) beendet wird:
$ yes Wiederholt standardmäßig "y".
y
y
y ^C Beendet den Befehl mit Strg-C.
$ yes woof! Wiederholt einen anderen String.
woof!
woof!
woof! ^C
Wozu ist dieses seltsame Verhalten nützlich? yes kann interaktive Programme mit Eingaben versorgen, sodass sie unbeaufsichtigt laufen können. So könnte zum Beispiel das Programm fsck, das ein Linux-Dateisystem auf Fehler überprüft, den Benutzer auffordern, auf eine Antwort auf y oder n zu warten. Schickt man die Ausgabe des Befehls yes über eine Pipeline an fsck, dann antwortet sie in Ihrem Namen auf jede Eingabeaufforderung, sodass Sie einfach weggehen und fsck bis zum Ende durchlaufen lassen können.2
Die Hauptanwendung von yes besteht für unsere Zwecke darin, einen String eine bestimmte Anzahl mal ausgeben zu lassen, indem yes über eine Pipe an head geleitet wird (Sie werden in »Testdateien generieren« auf Seite 169 ein praktisches Beispiel sehen):
$ yes "Efficient Linux" | head -n3 Gibt einen String dreimal aus.
Efficient Linux
Efficient Linux
Efficient Linux
Wenn Sie nur einen Teil einer Datei brauchen, sind die einfachsten Befehle, die Sie hier kombinieren können, grep, cut, head und tail. Sie haben die ersten drei bereits in Kapitel 1 gesehen: grep gibt Zeilen aus, die einem String entsprechen, cut gibt Spalten aus einer Datei aus, und head gibt die ersten Zeilen einer Datei aus. Ein neuer Befehl, tail, ist das Gegenstück zu head und gibt die letzten Zeilen einer Datei aus. Abbildung 5-1 stellt dar, wie diese vier Befehle zusammenarbeiten.
Abbildung 5-1: head, grep und tail extrahieren Zeilen, und cut extrahiert Spalten. In diesem Beispiel erfasst grep Zeilen, die den String »blandit« enthalten.
In diesem Abschnitt tauche ich tiefer in grep ein, das mehr macht, als nur einfache Strings zu erfassen. Außerdem erkläre ich tail etwas genauer. Darüber hinaus schauen wir uns schon einmal eine Eigenschaft des Befehls awk an, die in der Lage ist, Spalten auf eine Weise zu extrahieren, die cut nicht beherrscht. Diese fünf Befehle zusammen können so ziemlich jeden Text isolieren, und das in einer einzigen Pipeline.
Sie haben bereits gesehen, wie grep Zeilen aus einer Datei ausgibt, die zu einem bestimmten String passen:
$ cat frost
Whose woods these are I think I know.
His house is in the village though;
He will not see me stopping here
To watch his woods fill up with snow.
This is not the end of the poem.
$ grep his frost Gibt Zeilen aus, die "his" enthalten.
To watch his woods fill up with snow.
This is not the end of the poem. "This" enthält "his".
grep besitzt einige ausgesprochen nützliche Optionen. Benutzen Sie die Option -w, um nur ganze Wörter zu erfassen:
$ grep -w his frost Erfasst exakt das Wort "his".
To watch his woods fill up with snow.
Mit der Option -i ignorieren Sie die Groß-/Kleinschreibung von Buchstaben:
$ grep -i his frost
His house is in the village though; Erfasst "His".
To watch his woods fill up with snow. Erfasst "his".
This is not the end of the poem. "This" entspricht "his".
Verwenden Sie die Option -l, um nur die Namen der Dateien auszugeben, die passende Zeilen enthalten, nicht jedoch die passenden Zeilen selbst:
$ grep -l his * Welche Dateien enthalten den String "his"?
frost
Die wirkliche Macht von grep entfaltet sich jedoch erst, wenn Sie über das Abgleichen einfacher Strings hinausgehen und Muster erfassen, sogenannte reguläre Ausdrücke.3 Die Syntax unterscheidet sich von Dateinamensmustern; eine teilweise Beschreibung finden Sie in Tabelle 5-1.
Tabelle 5-1: Syntax einiger regulärer Ausdrücke für grep, awk und sed?4
Um dies zu erfassen … |
… benutzen Sie diese Syntax: |
Beispiel |
Zeilenanfang |
^ |
^a = Zeile, die mit a beginnt |
Zeilenende |
$ |
!$ = Zeile, die mit einem Ausrufezeichen endet |
Jedes einzelne Zeichen (ausgenommen Newline) |
. |
… = drei beliebige aufeinanderfolgende Zeichen |
Ein wortwörtliches Caret, ein Dollarzeichen oder ein anderes Sonderzeichen c |
\c |
\$ = ein wortwörtliches Dollarzeichen |
Null oder mehr Vorkommen des Ausdrucks E |
E* |
_* = null oder mehr Unterstriche |
Jedes einzelne Zeichen in einer Menge |
[Zeichen] |
[aeiouAEIOU] = jeder Vokal |
Jedes einzelne Zeichen, das nicht in einer Menge ist |
[^Zeichen] |
[^aeiouAEIOU] = jeder Nichtvokal |
Jedes Zeichen in einem gegebenen Bereich zwischen c1 und c2 |
[c1-c2] |
[0-9] = jede Ziffer |
Jedes Zeichen, das nicht in einem gegebenen Bereich zwischen c1 und c2 liegt |
[^c1-c2] |
[^0-9] = jede Nichtziffer |
Einer von zwei Ausdrücken E1 oder E2 |
E1\|E2 für grep und sed |
one\|two = entweder one oder two |
|
E1|E2 für awk |
one|two = entweder one oder two |
Gruppieren des Ausdrucks E, um eine Präzedenz vorzugeben |
\(E\) für grep und sed5 |
\(one\|two\)* = null oder mehr Vorkommen von one oder two |
|
(E) für awk |
(one|two)* = null oder mehr Vorkommen von one oder two |
Hier sind einige Beispiel-grep-Befehle mit regulären Ausdrücken. Erfassen Sie alle Zeilen, die mit einem Großbuchstaben beginnen:
$ grep '^[A-Z]' myfile
Erfassen Sie alle nicht leeren Zeilen (d. h., erfassen Sie alle leeren Zeilen und nutzen Sie -v, um sie wegzulassen):
$ grep -v '^$' myfile
Erfassen Sie alle Zeilen, die entweder cookie oder cake enthalten:
$ grep 'cookie\|cake' myfile
Erfassen Sie alle Zeilen, die wenigstens fünf Zeichen lang sind:
$ grep '.....' myfile
Erfassen Sie alle Zeilen, in denen ein Kleiner-als-Symbol irgendwo vor einem Größer-als-Symbol auftaucht, wie etwa in Zeilen aus HTML-Code:
$ grep '<.*>' page.html
Reguläre Ausdrücke sind toll, aber manchmal geraten sie einem auch in die Quere. Nehmen wir einmal an, Sie wollten nach den zwei Zeilen in der Datei frost suchen, die ein w gefolgt von einem Punkt enthalten. Der folgende Befehl erzeugt die falschen Ergebnisse, weil ein Punkt ein regulärer Ausdruck ist, der »jedes Zeichen« bedeutet:
$ grep w. frost
Whose woods these are I think I know.
He will not see me stopping here
To watch his woods fill up with snow.
Um dieses Problem zu umgehen, können Sie das Sonderzeichen mit einem Escape-Zeichen schützen:
$ grep 'w\.' frost
Whose woods these are I think I know.
To watch his woods fill up with snow.
Aber diese Lösung nervt sehr schnell, wenn Sie viele Sonderzeichen schützen müssen. Glücklicherweise können Sie grep zwingen, alles über reguläre Ausdrücke zu vergessen und nach jedem Zeichen wörtlich in der Eingabe zu suchen. Dazu verwenden Sie die Option -F (fixed) oder führen, als Alternative mit äquivalenten Ergebnissen, fgrep anstelle von grep aus:
$ grep -F w. frost
Whose woods these are I think I know.
To watch his woods fill up with snow.
$ fgrep w. frost
Whose woods these are I think I know.
To watch his woods fill up with snow.
grep besitzt viele weitere Optionen; ich werde noch eine vorstellen, die ein häufig auftretendes Problem löst. Verwenden Sie die Option -f (kleingeschrieben, nicht mit -F verwechseln), um anstelle eines einzelnen Strings eine Menge von Strings zu erfassen. Als praktisches Beispiel wollen wir alle Shells auflisten, die in der Datei /etc/passwd zu finden sind, die ich in »Befehl #5: sort« auf Seite 28 vorgestellt habe. Wie Sie sich erinnern werden, enthält jede Zeile in /etc/passwd Informationen über einen Benutzer, organisiert als Felder, die durch Doppelpunkte getrennt sind. Das letzte Feld auf jeder Zeile enthält das Programm, das gestartet wird, wenn sich der Benutzer einloggt. Dieses Programm ist oft, aber nicht immer eine Shell:
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash 7. Feld ist eine Shell.
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin 7. Feld ist keine Shell.
...
Wie können Sie feststellen, ob ein Programm eine Shell ist? Nun, die Datei/etc/shells listet alle gültigen Login-Shells in einem Linux-System auf:
$ cat /etc/shells
/bin/sh
/bin/bash
/bin/csh
Sie können also alle gültigen Shells in /etc/passwd auflisten, indem Sie das siebte Feld mit cut extrahieren, Duplikate mit sort -u eliminieren und die Ergebnisse mit grep -f an /etc/shells prüfen. Aus Sicherheitsgründen füge ich außerdem die Option -F hinzu, sodass alle Zeilen in /etc/shells wörtlich verarbeitet werden, selbst wenn sie Sonderzeichen enthalten:
$ cut -d: -f7 /etc/passwd | sort -u | grep -f /etc/shells -F
/bin/bash
/bin/sh
Der Befehl tail gibt die letzten Zeilen einer Datei aus – standardmäßig zehn Zeilen. Er ist das Gegenstück zum Befehl head. Nehmen wir einmal an, Sie hätten eine Datei namens alphabet, die 26 Zeilen enthält, eine pro Buchstabe:
$ cat alphabet
A is for aardvark
B is for bunny
C is for chipmunk
...
X is for xenorhabdus
Y is for yak
Z is for zebu
Geben Sie die letzten drei Zeilen mit tail aus. Die Option -n legt die Anzahl der auszugebenden Zeilen fest, analog zu head:
$ tail -n3 alphabet
X is for xenorhabdus
Y is for yak
Z is for zebu
Falls Sie vor die Zahl ein Pluszeichen setzen (+), beginnt die Ausgabe bei der Zeile mit dieser Nummer und wird bis zum Ende der Datei fortgesetzt. Der folgende Befehl beginnt an der 25. Zeile der Datei:
$ tail -n+25 alphabet
Y is for yak
Z is for zebu
Kombinieren Sie tail und head, um einen beliebigen Bereich an Zeilen aus einer Datei auszugeben. Um zum Beispiel nur die vierte Zeile auszugeben, extrahieren Sie nur die ersten vier Zeilen und isolieren die letzte:
$ head -n4 alphabet | tail -n1
D is for dingo
Um ganz allgemein die Zeilen M bis N auszugeben, extrahieren Sie die ersten N Zeilen mit head und isolieren dann die letzten N-M+1 Zeilen mit tail. Geben Sie die Zeilen sechs bis acht der Datei alphabet aus:
$ head -n8 alphabet | tail -n3
F is for falcon
G is for gorilla
H is for hawk
Tipp head und tail unterstützen beide eine einfachere Syntax zum Festlegen einer Zeilenzahl ohne -n. Diese Syntax ist schon ziemlich alt, nicht dokumentiert und gilt als überholt, wird aber wahrscheinlich noch bis in alle Ewigkeit unterstützt werden: |
|
|
$ head -4 alphabet das Gleiche wie head -n4 alphabet $ tail -3 alphabet das Gleiche wie tail -n3 alphabet $ tail +25 alphabet das Gleiche wie tail -n+25 alphabet |
Der Befehl awk ist eine allgemeine Textverarbeitung mit Hunderten von Anwendungen. Werfen wir mal einen Blick auf eine kleine Funktion namens print, die Spalten aus einer Datei auf eine Weise extrahiert, die cut nicht beherrscht. Betrachten Sie die Systemdatei/etc/hosts, in der IP-Adressen und Hostnamen enthalten sind, die jeweils durch beliebig viel Whitespace getrennt werden:
$ less /etc/hosts
127.0.0.1 localhost
127.0.1.1 myhost myhost.example.com
192.168.1.2 frodo
192.168.1.3 gollum
192.168.1.28 gandalf
Stellen Sie sich nun vor, Sie wollten die Hostnamen isolieren, indem Sie das zweite Wort auf jeder Zeile ausgeben. Die Herausforderung liegt darin, dass vor jedem Hostnamen eine beliebige Menge an Whitespace steht. Bei cut müssen die Spalten entweder ordentlich anhand der Spaltennummer aufgereiht (-c) oder durch ein einzelnes, konsistentes Zeichen getrennt sein (-f). Sie brauchen einen Befehl, der das zweite Wort auf jeder Zeile ausgibt, was awk mit Leichtigkeit schafft:
$ awk '{print $2}' /etc/hosts
localhost
myhost
frodo
gollum
gandalf
awk verweist auf eine Spalte mit einem Dollarzeichen, gefolgt von einer Spaltennummer: zum Beispiel $7 für die siebte Spalte. Falls die Spaltennummer mehr als eine Stelle aufweist, umgeben Sie die Zahl mit runden Klammern: zum Beispiel $(25). Um auf das letzte Feld zu verweisen, benutzen Sie $NF (Number of Fields, Feldanzahl). Um die ganze Zeile anzusprechen, verwenden Sie $0.
awk gibt standardmäßig zwischen den Werten keinen Whitespace aus. Sollten Sie Whitespace wünschen, trennen Sie die Werte mit Kommata:
$ echo Efficient fun Linux | awk '{print $1 $3}' kein Whitespace
EfficientLinux
$ echo Efficient fun Linux | awk '{print $1, $3}' Whitespace
Efficient Linux
Die awk-Anweisung print eignet sich hervorragend für die Verarbeitung der Ausgaben von Befehlen, die keine sauberen und ordentlichen Spalten liefern. Ein Beispiel ist df, das die Menge an freiem und benutztem Festplattenplatz auf einem Linux-System ausgibt:
$ df / /data
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda1 1888543276 902295944 890244772 51% /
/dev/sda2 7441141620 1599844268 5466214400 23% /data
Die Lage der Spalten kann je nach Länge der Filesystem-Pfade, der Plattengrößen und der Optionen, die Sie an df übergeben, variieren, sodass sich die Werte nicht zuverlässig mit cut extrahieren lassen. Mit awk dagegen können Sie leicht zum Beispiel den vierten Wert auf jeder Zeile, der den verfügbaren Festplattenplatz repräsentiert, isolieren:
$ df / /data | awk '{print $4}'
Available
890244772
5466214400
und sogar gleichzeitig die erste Zeile (den Header) mit ein bisschen awk-Zauberei entfernen, die nur Zeilennummern ausgibt, die größer als 1 sind:
$ df / /data | awk 'FNR>1 {print $4}'
890244772
5466214400
Falls Ihnen eine Eingabe begegnet, die durch etwas anderes als Leerzeichen abgetrennt ist, kann awk das Feldtrennzeichen mit der Option -F in einen regulären Ausdruck ändern:
$ echo efficient:::::linux | awk -F':*' '{print $2}' beliebige Anzahl von Doppelpunkten
linux
Mehr über awk erfahren Sie in »Die awk-Grundlagen« auf Seite 106.
Sie kennen bereits mehrere Befehle, die Text aus unterschiedlichen Dateien miteinander kombinieren. Der erste ist cat, der den Inhalt mehrerer Dateien auf der Standardausgabe ausgibt. Dateien werden von oben nach unten vereint bzw. verkettet. Daher stammt auch der Name – das englische Wort für verketten lautet »concatenate«:
$ cat poem1
It is an ancient Mariner,
And he stoppeth one of three.
$ cat poem2
'By thy long grey beard and glittering eye,
$ cat poem3
Now wherefore stopp'st thou me?
$ cat poem1 poem2 poem3
It is an ancient Mariner,
And he stoppeth one of three.
'By thy long grey beard and glittering eye,
Now wherefore stopp'st thou me?
Der zweite Befehl zum Kombinieren von Text, den Sie bereits gesehen haben, ist echo, das Shell-Builtin, das alle Argumente, die Sie ihm übergeben, getrennt durch ein einzelnes Leerzeichen, ausgibt. Es kombiniert Strings nebeneinander:
Schauen wir uns noch weitere Befehle an, die Text kombinieren:
tac
Kombiniert Textdateien von unten nach oben.
paste
Kombiniert Textdateien nebeneinander.
diff
Ein Befehl, der Text aus zwei Dateien verschachtelt, indem er deren Unterschiede ausgibt.
Der tac-Befehl kehrt eine Datei Zeile für Zeile um. Sein Name ist das Wort cat, aber rückwärts geschrieben.
$ cat poem1 poem2 poem3 | tac
Now wherefore stopp'st thou me?
'By thy long grey beard and glittering eye,
And he stoppeth one of three.
It is an ancient Mariner,
Beachten Sie, dass ich vor dem Umkehren des Texts drei Dateien verkettet habe. Übergebe ich stattdessen mehrere Dateien als Argumente an tac, kehrt es stattdessen die Zeilen der einzelnen Dateien um, was eine andere Ausgabe produziert:
$ tac poem1 poem2 poem3
And he stoppeth one of three. erste Datei umgekehrt
It is an ancient Mariner,
'By thy long grey beard and glittering eye, zweite Datei
Now wherefore stopp'st thou me? dritte Datei
tac eignet sich großartig zum Verarbeiten von Daten, die bereits in chronologischer Reihenfolge vorliegen, aber nicht mit dem Befehlsort -r umgekehrt werden können. Ein typischer Anwendungsfall ist das Umkehren einer Webserver-Logdatei, um deren Inhalt vom neuesten zum ältesten Eintrag verarbeiten zu können:
192.168.1.34 - - [30/Nov/2021:23:37:39 -0500] "GET / HTTP/1.1"...
192.168.1.10 - - [01/Dec/2021:00:02:11 -0500] "GET /notes.html HTTP/1.1"...
192.168.1.8 - - [01/Dec/2021:00:04:30 -0500] "GET /stuff.html HTTP/1.1"...
...
Die Zeilen sind in chronologischer Reihenfolge mit Zeitstempeln sortiert, jedoch nicht in alphabetischer oder numerischer Reihenfolge angeordnet, sodass uns der Befehl sort -r nicht weiterhilft. Der tac-Befehl kann diese Zeilen umkehren, ohne die Zeitstempel betrachten zu müssen.
Der Befehl paste kombiniert Dateien nebeneinander in Spalten, die durch ein einzelnes Tab-Zeichen getrennt sind. Er ist eine Art Partner für den cut-Befehl, der Spalten aus einer durch Tabs separierten Datei extrahiert:
$ cat title-words1
EFFICIENT
AT
COMMAND
$ cat title-words2
linux
the
line
$ paste title-words1 title-words2
EFFICIENT linux
AT the
COMMAND line
$ paste title-words1 title-words2 | cut -f2 cut & paste ergänzen einander
linux
the
line
Zum Ändern des Trennzeichens in ein anderes Zeichen, etwa ein Komma, verwenden Sie die Option -d (wie Delimiter):
$ paste -d, title-words1 title-words2
EFFICIENT,linux
AT,the
COMMAND,line
Transponieren Sie die Ausgabe, wodurch eingefügte Zeilen statt eingefügter Spalten entstehen, mit der Option -s:
$ paste -d, -s title-words1 title-words2
EFFICIENT,AT,COMMAND
linux,the,line
paste kann auch Daten aus zwei oder mehr Dateien verschachteln, wenn Sie das Trennzeichen zu einem Newline-Zeichen ändern (\n):
$ paste -d "\n" title-words1 title-words2
EFFICIENT
linux
AT
the
COMMAND
line
diff vergleicht zwei Dateien Zeile für Zeile und gibt einen Bericht über deren Unterschiede aus:
Linux is all about efficiency.
I hope you will enjoy this book.
$ cat file2
MacOS is all about efficiency.
I hope you will enjoy this book.
Have a nice day.
$ diff file1 file2
1c1
< Linux is all about efficiency.
---
> MacOS is all about efficiency.
2a3
> Have a nice day.
Die Notation 1c1 repräsentiert eine Änderung oder einen Unterschied zwischen diesen beiden Dateien. Das bedeutet, dass sich Zeile 1 in der ersten Datei von Zeile 1 in der zweiten Datei unterscheidet. Dieser Notation folgen dann die entsprechende Zeile aus file1, ein Trenner aus drei Bindestrichen (---) und die entsprechende Zeile aus file2. Das führende Symbol < kennzeichnet immer eine Zeile aus der ersten Datei, > kennzeichnet entsprechend eine Zeile aus der zweiten Datei.
Die Notation 2a3 repräsentiert einen Zusatz. Dies bedeutet, dass file2 eine dritte Zeile besitzt, die nach der zweiten Zeile von file1 nicht vorhanden ist. Dieser Notation folgt dann die zusätzliche Zeile aus file2, »Have a nice day«.
Die diff-Ausgabe kann andere Notationen enthalten und andere Formen annehmen. Diese kurze Erklärung reicht jedoch für unsere Hauptaufgabe, nämlich die Verwendung von diff als Textverarbeitung, die Zeilen aus zwei Dateien miteinander verschränkt. Viele Benutzer sehen diff nicht auf diese Weise, aber es eignet sich sehr gut zum Bilden von Pipelines, mit denen sich bestimmte Arten von Problemen lösen lassen. Zum Beispiel können Sie die sich unterscheidenden Zeilen mit grep und cut isolieren:
$ diff file1 file2 | grep '^[<>]'
< Linux is all about efficiency.
> MacOS is all about efficiency.
> Have a nice day.
$ diff file1 file2 | grep '^[<>]' | cut -c3-
Linux is all about efficiency.
MacOS is all about efficiency.
Have a nice day.
Praktische Beispiele finden Sie in »Technik #4: Prozesssubstitution« auf Seite 135 und »Zusammengehörende Dateipaare prüfen« auf Seite 165.
Kapitel 1 hat mehrere Befehle vorgestellt, die Text von der Standardeingabe lesen und auf der Standardausgabe in etwas anderes verwandeln. wc gibt die Anzahl von Zeilen, Wörtern und Zeichen aus, sort ordnet Zeilen in alphabetischer oder numerischer Reihenfolge an, und uniq konsolidiert doppelt vorhandene Zeilen. Sprechen wir über weitere Befehle, die ihre Eingaben transformieren:
tr
Übersetzt Zeichen in andere Zeichen.
rev
Kehrt die Zeichen auf einer Zeile um.
awk und sed
Allzwecktransformatoren.
tr übersetzt eine Gruppe von Zeichen in eine andere. Ich habe Ihnen in Kapitel 2 ein Beispiel für das Übersetzen von Doppelpunkten in Newline-Zeichen zum Ausgeben der Shell-Variablen PATH gezeigt:
$ echo $PATH | tr : "\n" Übersetzt Doppelpunkte in Newlines.
/home/smith/bin
/usr/local/bin
/usr/bin
/bin
/usr/games
/usr/lib/java/bin
tr nimmt zwei Mengen aus Zeichen als Argumente entgegen und übersetzt Mitglieder der ersten Menge in die entsprechenden Mitglieder der zweiten Menge. Gebräuchliche Anwendungsfälle sind das Konvertieren von Text in Groß- oder Kleinbuchstaben:
$ echo efficient | tr a-z A-Z Übersetzt a in A, b in B usw.
EFFICIENT
$ echo Efficient | tr A-Z a-z
efficient
das Umwandeln von Leerzeichen in Newlines:
$ echo Efficient Linux | tr " " "\n"
Efficient
Linux
und das Löschen von Whitespace mit der Option -d (delete):
$ echo efficient linux | tr -d ' \t' Entfernt Leerzeichen und Tabs.
efficientlinux
Der Befehl rev kehrt die Zeichen jeder Eingabezeile um:6
Neben dem offensichtlichen Unterhaltungsfaktor ist rev ganz praktisch für das Extrahieren von kniffligen Informationen aus Dateien. Nehmen wir einmal an, Sie hätten eine Datei mit den Namen von Prominenten:
$ cat celebrities
Jamie Lee Curtis
Zooey Deschanel
Zendaya Maree Stoermer Coleman
Rihanna
und wollten das letzte Wort auf jeder Zeile extrahieren (Curtis, Deschanel, Coleman, Rihanna). Das ließe sich leicht mit cut -f bewerkstelligen, wenn jede Zeile dieselbe Anzahl an Feldern hätte, aber leider variiert die Anzahl. Mit rev können Sie alle Zeilen umkehren, das erste Feld herausschneiden und das Ganze wieder umkehren, um so Ihr Ziel zu erreichen:7
$ rev celebrities
sitruC eeL eimaJ
lenahcseD yeooZ
nameloC remreotS eeraM ayadneZ
annahiR
$ rev celebrities | cut -d' ' -f1
sitruC
lenahcseD
nameloC
annahiR
$ rev celebrities | cut -d' ' -f1 | rev
Curtis
Deschanel
Coleman
Rihanna
awk und sed sind weithin einsetzbare »Superbefehle« zum Verarbeiten von Text. Sie können fast alles von dem erledigen, was auch die anderen Befehle in diesem Kapitel machen, allerdings mit einer Syntax, die kryptischer aussieht. Ein einfaches Beispiel für den Einstieg: Sie können genau wie head die ersten zehn Zeilen einer Datei ausgeben:
$ sed 10q myfile Gibt zehn Zeilen aus und beendet sich, dann (q).
$ awk 'FNR<=10' myfile Gibt Text aus, solange die Zeilennummer ≤ 10 ist.
Sie können aber auch Dinge, die andere Befehle nicht können, wie etwa das Ersetzen oder Vertauschen von Strings:
$ echo image.jpg | sed 's/\.jpg/.png/' Ersetzt .jpg durch .png.
image.png
$ echo "linux efficient" | awk '{print $2, $1}' Vertauscht zwei Wörter.
efficient linux
awk und sed sind schwieriger zu erlernen als die anderen Befehle, die ich bisher behandelt habe, weil in beide eine Art Mini-Programmiersprache eingebaut ist. Sie besitzen so viele Fähigkeiten, dass schon ganze Bücher über sie geschrieben wurden.8 Ich empfehle ganz dringend, sich die Zeit zu nehmen, beide Befehle (oder zumindest einen von ihnen) zu erlernen. Am Anfang unserer Reise stelle ich die jeweiligen Grundprinzipien der beiden Befehle vor und demonstriere einige gebräuchliche Anwendungen. Außerdem empfehle ich Ihnen ein paar Onlinetutorials, mit deren Hilfe Sie mehr über diese leistungsfähigen, wichtigen Befehle lernen können.
Keine Sorge, Sie müssen sich nicht jede einzelne Funktion oder Eigenschaft von awk oder sed merken. Erfolg mit diesen Befehlen bedeutet in Wirklichkeit:
awk transformiert Textzeilen aus Dateien (oder der Standardeingabe) in anderen Text. Dazu verwendet es eine Abfolge von Anweisungen, die als awk-Programm bezeichnet werden.9 Je geübter Sie im Schreiben von awk-Programmen werden, umso flexibler können Sie Text manipulieren. Sie können das awk-Programm auf der Kommandozeile angeben:
$ awk Programm Eingabedateien
Oder Sie speichern ein oder mehrere awk-Programme in Dateien und referenzieren sie mithilfe der Option -f. Die Programme werden dann nacheinander ausgeführt:
$ awk -f Programmdatei1 -f Programmdatei2 -f Programmdatei3 Eingabedateien
Ein awk-Programm enthält eine oder mehrere Aktionen, wie etwa das Berechnen von Werten oder das Ausgeben von Text, die ausgeführt werden, wenn eine Eingabezeile einem Muster entspricht. Jede Anweisung im Programm hat die Form:
Muster {Aktion}
Zu den typischen Mustern gehören:
Dessen Aktion wird nur einmal ausgeführt, und zwar bevor awk irgendwelche Eingaben verarbeitet.
Das Wort END
Dessen Aktion wird nur einmal ausgeführt, und zwar nachdem awk alle Eingaben verarbeitet hat.
Ein regulärer Ausdruck (siehe Tabelle 5-1), umgeben von Schrägstrichen
Ein Beispiel ist /^[A-Z]/ zum Erfassen von Zeilen, die mit einem Großbuchstaben beginnen.
Andere awk-spezifische Ausdrücke
Um zum Beispiel zu prüfen, ob das dritte Feld auf einer Eingabezeile ($3) mit einem Großbuchstaben beginnt, würde das Muster so aussehen: $3~/^[A-Z]/. Ein anderes Beispiel ist FNR>5, das awk anweist, die ersten fünf Zeilen der Eingabe zu überspringen.
Eine Aktion ohne Muster wird für jede Zeile der Eingabe ausgeführt. (Mehrere awk-Programme in »Der Befehl awk {print}« auf Seite 99 waren von diesem Typ.) Beispielsweise löst awk ganz elegant das »Gib den Nachnamen der Prominenten aus«-Problem aus »Der rev-Befehl« auf Seite 104, indem es direkt das letzte Wort jeder Zeile ausgibt:
$ awk '{print $NF}' celebrities
Curtis
Deschanel
Coleman
Rihanna
Tipp Wenn Sie ein awk-Programm auf der Kommandozeile aufrufen, schließen Sie es in Anführungszeichen ein, um zu verhindern, dass die Shell die Sonderzeichen von awk auswertet. Verwenden Sie je nach Bedarf einfache oder doppelte Anführungszeichen. |
Ein Muster ohne Aktion führt ganz einfach die Standardaktion {print} aus, bei der alle passenden Eingabezeilen unverändert ausgegeben werden:
$ echo efficient linux | awk '/efficient/'
efficient linux
Für eine umfassendere Demonstration verarbeiten Sie die durch Tabulatoren getrennte Datei animals.txt aus Beispiel 1-1, um eine ordentliche Bibliografie herzustellen, wobei Zeilen aus diesem Format:
python Programming Python 2010 Lutz, Mark
in dieses Format konvertiert werden:
Lutz, Mark (2010). "Programming Python"
Dazu müssen drei Felder neu angeordnet und einige Zeichen, wie runde Klammern und doppelte Anführungszeichen, hinzugefügt werden. Das folgende awk-Programm erledigt das für uns. Dabei setzt es die Option -F ein, um das eingegebene Trennzeichen von Leerzeichen in Tabulatoren zu ändern (\t):
$ awk -F'\t' '{print $4, "(" $3 ").", "\"" $2 "\""}' animals.txt
Lutz, Mark (2010). "Programming Python"
Barrett, Daniel (2005). "SSH, The Secure Shell"
Schwartz, Randal (2012). "Intermediate Perl"
Bell, Charles (2014). "MySQL High Availability"
Siever, Ellen (2009). "Linux in a Nutshell"
Boney, James (2005). "Cisco IOS in a Nutshell"
Roman, Steven (1999). "Writing Word Macros"
Fügen Sie einen regulären Ausdruck hinzu, um nur das »horse«-Buch zu verarbeiten:
$ awk -F'\t' '/^horse/{print $4, "(" $3 ").", "\"" $2 "\""}' animals.txt
Siever, Ellen (2009). "Linux in a Nutshell"
Oder verarbeiten Sie lediglich Bücher von 2010 oder später, indem Sie testen, ob Feld $3 diesem hier entspricht: ^201:
$ awk -F'\t' '$3~/^201/{print $4, "(" $3 ").", "\"" $2 "\""}' animals.txt
Lutz, Mark (2010). "Programming Python"
Schwartz, Randal (2012). "Intermediate Perl"
Bell, Charles (2014). "MySQL High Availability"
Fügen Sie schließlich eine BEGIN-Anweisung hinzu, um eine freundliche Überschrift auszugeben, einige Bindestriche zum Einrücken und eine END-Anweisung, um den Leser auf weitere Informationen hinzuweisen:
$ awk -F'\t' \
'BEGIN {print "Recent books:"} \
$3~/^201/{print "-", $4, "(" $3 ").", "\"" $2 "\""} \
END {print "For more books, search the web"}' \
animals.txt
Recent books:
- Lutz, Mark (2010). "Programming Python"
- Schwartz, Randal (2012). "Intermediate Perl"
- Bell, Charles (2014). "MySQL High Availability"
For more books, search the web
awk kann noch viel mehr als einfach nur ausgeben – es kann auch Berechnungen durchführen, wie etwa das Addieren der Zahlen 1 bis 100:
$ seq 1 100 | awk '{s+=$1} END {print s}'
5050
Um mehr über awk zu lernen als das, was auf ein paar mickrigen Buchseiten behandelt werden kann, nehmen Sie sich eines der awk-Tutorials unter tutorialspoint.com/awk (https://www.tutorialspoint.com/awk/) oder riptutorial.com/awk (https://riptutorial.com/awk) vor, oder suchen Sie im Web nach »awk Tutorial«. Sie werden froh sein, wenn Sie es gemacht haben.
In »Dateiduplikate entdecken« auf Seite 33 haben Sie eine Pipeline gebaut, die doppelt vorhandene JPEG-Dateien mithilfe der Prüfsumme erkennt und zählt. Allerdings war diese Lösung nicht leistungsfähig genug, um die Dateinamen auszugeben:
$ md5sum *.jpg | cut -c1-32 | sort | uniq -c | sort -nr | grep -v " 1 "
3 f6464ed766daca87ba407aede21c8fcc
2 c7978522c58425f6af3f095ef1de1cd5
2 146b163929b6533f02e91bdf21cb9563
Mit awk stehen Ihnen Werkzeuge zur Verfügung, auch die Dateinamen auszugeben. Konstruieren wir uns einen neuen Befehl, der jede Zeile der md5sum-Ausgabe liest:
$ md5sum *.jpg
146b163929b6533f02e91bdf21cb9563 image001.jpg
63da88b3ddde0843c94269638dfa6958 image002.jpg
146b163929b6533f02e91bdf21cb9563 image003.jpg
...
und nicht nur die Vorkommen der einzelnen Prüfsummen zählt, sondern auch die Dateinamen für die Ausgabe speichert. Sie brauchen zwei zusätzliche awk-Funktionen: Arrays und Loops.
Ein Array ist eine Variable, die eine Sammlung von Werten enthält. Wenn das Array den Namen A trägt und sieben Werte aufgenommen hat, könnten die Werte als A[1], A[2], A[3] bis A[7] angesprochen werden. Die Werte 1 bis 7 werden als Schlüssel des Arrays bezeichnet. A[1] bis A[7] sind die Elemente des Arrays. Sie können jedoch beliebige Schlüssel anlegen. Falls Sie auf die sieben Elemente Ihres Arrays lieber mithilfe der Namen von Disneyfiguren zugreifen, nennen Sie sie einfach entsprechend A["Chef"],A["Brummbaer"], A["Pimpel"] bis zu A["Schlafmuetz"].
Um die Duplikate von Bildern zu zählen, erzeugen Sie ein Array namens counts mit einem Element für jede der Prüfsummen. Jeder Array-Schlüssel ist eine Prüfsumme, und die damit verknüpften Elemente geben an, wie oft die Prüfsumme in der Eingabe auftaucht. Zum Beispiel könnte das Array-Element counts["f6464ed766daca87ba407aede21c8fcc"] den Wert 3 haben. Das folgende awk-Skript untersucht jede Zeile der md5sum-Ausgabe, isoliert die Prüfsumme ($1) und benutzt sie als Schlüssel für das counts-Array. Der Operator ++ inkrementiert ein Element jedes Mal um 1, wenn awk die damit verknüpfte Prüfsumme entdeckt:
$ md5sum *.jpg | awk '{counts[$1]++}'
Bisher erzeugt das awk-Skript keine Ausgabe – es zählt lediglich die Prüfsummen und beendet sich dann. Um die Zählerwerte auszugeben, brauchen Sie eine zweite awk-Funktion, nämlich eine sogenannte for-Schleife. Eine for-Schleife geht Schlüssel für Schlüssel durch ein Array und verarbeitet die Elemente nacheinander. Dabei verwendet es folgende Syntax:
for (Variable in Array) mache etwas mit Array[Variable]
Gib zum Beispiel jedes Array-Element anhand seines Schlüssels aus:
for (key in counts) print array[key]
Platzieren Sie diese Schleife in die END-Anweisung, sodass sie ausgeführt wird, nachdem alle Zähler berechnet wurden.
$ md5sum *.jpg \
| awk '{counts[$1]++} \
END {for (key in counts) print counts[key]}'
1
2
2
...
Fügen Sie als Nächstes die Prüfsummen zur Ausgabe hinzu. Jeder Array-Schlüssel ist eine Prüfsumme, deshalb geben Sie den Schlüssel einfach hinter dem Zähler aus:
$ md5sum *.jpg \
| awk '{counts[$1]++} \
END {for (key in counts) print counts[key] " " key}'
1 714eceeb06b43c03fe20eb96474f69b8
2 146b163929b6533f02e91bdf21cb9563
2 c7978522c58425f6af3f095ef1de1cd5
...
Um die Dateinamen zu sammeln und auszugeben, benutzen Sie ein zweites Array, names, ebenfalls mit Prüfsummen als Schlüssel. Wenn awk die einzelnen Zeilen der Ausgabe verarbeitet, fügen Sie den Dateinamen ($2) sowie ein Leerzeichen als Trennzeichen zu dem entsprechenden Element des names-Arrays hinzu. Geben Sie in der END-Schleife nach dem Ausgeben der Prüfsumme (key) einen Doppelpunkt sowie die gesammelten Dateinamen für diese Prüfsumme aus:
$ md5sum *.jpg \
| awk '{counts[$1]++; names[$1]=names[$1] " " $2} \
END {for (key in counts) print counts[key] " " key ":" names[key]}'
1 714eceeb06b43c03fe20eb96474f69b8: image011.jpg
2 146b163929b6533f02e91bdf21cb9563: image001.jpg image003.jpg
2 c7978522c58425f6af3f095ef1de1cd5: image019.jpg image020.jpg
...
Zeilen, die mit 1 beginnen, repräsentieren Prüfsummen, die nur einmal auftreten, also keine Duplikate sind. Leiten Sie die Ausgabe mit einer Pipe an grep -v, um diese Zeilen zu entfernen, sortieren Sie die Ergebnisse dann mit sort +nr numerisch von hohen zu niedrigen Werten, und Sie haben Ihre gewünschte Ausgabe:
$ md5sum *.jpg \
| awk '{counts[$1]++; names[$1]=names[$1] " " $2} \
END {for (key in counts) print counts[key] " " key ":" names[key]}' \
| grep -v '^1 ' \
| sort -nr
3 f6464ed766daca87ba407aede21c8fcc: image007.jpg image012.jpg image014.jpg
2 c7978522c58425f6af3f095ef1de1cd5: image019.jpg image020.jpg
2 146b163929b6533f02e91bdf21cb9563: image001.jpg image003.jpg
sed verwandelt genau wie awk Text aus Dateien (oder von der Standardeingabe) in irgendwelchen anderen Text. Dazu verwendet es eine Abfolge von Anweisungen, die als sed-Skript bezeichnet wird.10 sed-Skripte sehen auf den ersten Blick ziemlich kryptisch aus. Ein Beispiel ist s/Windows/Linux/g, das jedes Vorkommen des Strings Windows durch Linux ersetzt. Das Wort Skript meint hier keine Datei (wie bei einem Shell-Skript), sondern einen String.11 Rufen Sie sed mit einem Skript auf der Kommandozeile auf:
$ sed Skript Eingabedateien
oder verwenden Sie die Option -e, um mehrere Skripte zu übergeben, die die Eingabe nacheinander verarbeiten:
$ sed -e Skript1 -e Skript2 -e Skript3 Eingabedateien
Sie können sed-Skripte auch in Dateien speichern und sie mithilfe der Option-f ansprechen. Sie werden dann nacheinander ausgeführt:
$ sed -f Skriptdatei1 -f Skriptdatei2 -f Skriptdatei Eingabedateien
Wie bei awk hängt der Nutzen von sed von Ihrem Können beim Erstellen von sed-Skripten ab. Die gebräuchlichste Art von Skript ist ein Substitutionsskript, das Strings durch andere Strings ersetzt. Die Syntax ist:
s/regexp/Ersatz/
wobei regexp ein regulärer Ausdruck ist, der mit jeder Eingabezeile abgeglichen wird (siehe Tabelle 5-1), und Ersatz einen String darstellt, der den entsprechend passenden Text ersetzt. Ändern Sie als einfaches Beispiel ein Wort in ein anderes:
$ echo Efficient Windows | sed "s/Windows/Linux/"
Efficient Linux
Tipp Wenn Sie ein sed-Skript auf der Kommandozeile angeben, sollten Sie es in Anführungszeichen einschließen, um zu verhindern, dass die Shell die Sonderzeichen von sed auswertet. Verwenden Sie nach Bedarf einfache oder doppelte Anführungszeichen. |
sed löst mithilfe eines regulären Ausdrucks ganz leicht das »Gib den Nachnamen der Prominenten aus«-Problem aus »Der rev-Befehl« auf Seite 104. Erfassen Sie einfach alle Zeichen (.*) bis zum letzten Leerzeichen und ersetzen Sie sie durch nichts:
$ sed 's/.* //' celebrities
Curtis
Deschanel
Coleman
Rihanna
Sie könnten auf eine Substitution noch weitere Optionen folgen lassen, um deren Verhalten zu beeinflussen. Die Option i sorgt dafür, dass die Groß- und Kleinschreibung keine Rolle mehr spielt:
$ echo Efficient Stuff | sed "s/stuff/linux/" Schreibweise wichtig; kein Treffer.
Efficient Stuff
$ echo Efficient Stuff | sed "s/stuff/linux/i" Groß-/Kleinschreibung unwichtig, Treffer.
Efficient linux
Die Option g (global) ersetzt nicht nur das erste, sondern alle Vorkommen des regulären Ausdrucks:
$ echo efficient stuff | sed "s/f/F/" Ersetzt nur das erste "f".
eFficient stuff
$ echo efficient stuff | sed "s/f/F/g" Ersetzt alle Vorkommen von "f".
eFFicient stuFF
Eine andere gebräuchliche Art von sed-Skript ist das Löschskript. Es entfernt Zeilen anhand ihrer Zeilennummer:
$ seq 10 14 | sed 4d Entfernt die 4. Zeile.
10
11
12
14
oder Zeilen, die durch einen regulären Ausdruck erfasst werden:
$ seq 101 200 | sed '/[13579]$/
d' Löscht Zeilen, die auf eine ungerade Ziffer enden.
102
104
106
...
200
Angenommen, Sie hätten einige Dateinamen:
$ ls
image.jpg.1 image.jpg.2 image.jpg.3
und wollten neue Namen erzeugen: image1.jpg, image2.jpg und image3.jpg. sed kann die Dateinamen in mehrere Teile zerlegen und sie über eine Eigenschaft namens Teilausdrücke neu anordnen. Erzeugen Sie zuerst einen regulären Ausdruck, der die Dateinamen erfasst:
image\.jpg\.[1-3]
Sie wollen die letzte Ziffer weiter vorn im Dateinamen haben. Isolieren Sie sie deshalb, indem Sie sie mit den Symbolen \( und \) umgeben. Dies definiert einen Teilausdruck – einen designierten Teil eines regulären Ausdrucks:
image\.jpg\.\([1-3]\)
sed kann Teilausdrücke anhand der Nummer referenzieren und sie manipulieren. Sie haben nur einen Teilausdruck erzeugt, deshalb ist sein Name \1. Ein zweiter Teilausdruck wäre \2, und so weiter, bis zum Höchstwert\9. Ihre neuen Dateinamen hätten die Form image\1.jpg. Ihr sed-Skript sieht daher so aus:
$ ls | sed "s/image\.jpg\.\([1-3]\)/image\1.jpg/"
image1.jpg
image2.jpg
image3.jpg
Um die Dinge komplizierter zu machen, nehmen wir einmal an, dass die Dateinamen mehr Variationen aufweisen und aus kleingeschriebenen Wörtern bestehen würden:
$ ls
apple.jpg.1 banana.png.2 carrot.jpg.3
Erzeugen Sie drei Teilausdrücke, um den Basisdateinamen, die Erweiterung und die letzte Ziffer zu erfassen:
\([a-z][a-z]*\) \1 = Basisdateiname aus einem oder mehreren Buchstaben
\([a-z][a-z][a-z]\) \2 = Dateierweiterung aus drei Buchstaben
\([0-9]\) \3 = eine Ziffer
Verbinden Sie sie mit geschützten Punkten (\.) zu diesem regulären Ausdruck:
\([a-z][a-z]*\)\.\([a-z][a-z][a-z]\)\.\([0-9]\)
Präsentieren Sie sed die neu umgeformten Dateinamen als \1\3.\2. Die Substitution mit sed sieht nun so aus:
$ ls | sed "s/\([a-z][a-z]*\)\.\([a-z][a-z][a-z]\)\.\([0-9]\)/\1\3.\2/"
apple1.jpg
banana2.png
carrot3.jpg
Dieser Befehl benennt die Dateien nicht um, sondern gibt nur die neuen Namen aus. Der Abschnitt »Einen Dateinamen in eine Sequenz einfügen« auf Seite 162 zeigt ein ähnliches Beispiel, das auch noch die Umbenennung durchführt.
Um mehr über sed zu erfahren, als auf ein paar mickrigen Buchseiten behandelt werden kann, nehmen Sie sich eines der sed-Tutorials unter tutorialspoint.com/sed (https://www.tutorialspoint.com/sed) oder grymoire.com/Unix/Sed.html (https://www.grymoire.com/Unix/Sed.html) vor, oder suchen Sie im Web nach »sed Tutorial«.
Die meisten Linux-Systeme enthalten Tausende von Kommandozeilenprogrammen, und die meisten von diesen haben zahllose Optionen, die deren Verhalten ändern. Sie werden mit hoher Wahrscheinlichkeit weder alle kennenlernen noch sie sich merken können. Wie finden Sie deshalb im Ernstfall ein neues Programm – oder biegen sich ein Programm, das Sie bereits kennen, zurecht –, um Ihre Ziele zu erreichen?
Ihr erster (offensichtlicher) Schritt ist der Einsatz einer Websuchmaschine. Falls Sie zum Beispiel einen Befehl brauchen, der die Breite der Zeilen in einer Textdatei beschränkt, indem er Zeilen umbricht, die zu lang sind, suchen Sie im Web nach »Linux command wrap lines«, und Sie werden auf den Befehl fold hingewiesen:
$ cat title.txt
This book is titled "Efficient Linux at the Command Line"
$ fold -w40 title.txt
This book is titled "Efficient Linux at
the Command Line"
Um Befehle zu entdecken, die bereits auf Ihrem Linux-System installiert sind, führen Sie den Befehl man -k (oder äquivalent apropos) aus. Wenn Sie ein Wort angeben, sucht man -k in den kurzen Beschreibungen am Anfang der Manpages nach diesem Wort:
$ man -k width
DisplayWidth (3) - image format functions and macros
DisplayWidthMM (3) - image format functions and macros
fold (1) - wrap each input line to fit in specified width
...
man -k akzeptiert in Suchstrings reguläre Ausdrücke im awk-Stil (siehe Tabelle 5-1):
$ man -k "wide|width"
Ein Befehl, der auf Ihrem System nicht installiert ist, könnte immer noch durch den Paketmanager Ihres Systems installierbar sein. Ein Paketmanager ist eine Software zum Installieren von Linux-Programmen, die für Ihr System unterstützt werden. Populäre Paketmanager sind unter anderem apt, dnf, emerge, pacman, rpm, yum und zypper. Mithilfe des Befehls man können Sie ermitteln, welcher Paketmanager auf Ihrem System installiert ist und wie Sie nach nicht installierten Paketen suchen. Oft handelt es sich um eine Abfolge von zwei Befehlen: einem Befehl zum Kopieren der neuesten Daten über verfügbare Pakete (Metadaten) aus dem Internet auf Ihr System und einem weiteren zum Durchsuchen der Metadaten. So lauten zum Beispiel die Befehle für Ubuntu- oder Debian-Linux-basierte Systeme:
$ sudo apt update Herunterladen der neuesten Metadaten
$ apt-file search String Suchen nach einem String
Falls Sie auch nach langer Suche keinen passenden Befehl für Ihre Bedürfnisse finden oder konstruieren können, sollten Sie vielleicht in einem Onlineforum um Hilfe bitten. Ein großartiger Ausgangspunkt zum Stellen von Fragen ist die Hilfeseite »How Do I Ask a Good Question?« (https://oreil.ly/J0jho) von Stack Overflow. Wenn Sie beim Formulieren Ihrer Fragen deutlich machen, dass Sie die Zeit der anderen Personen zu schätzen wissen, werden die Experten eher geneigt sein, Ihnen zu antworten. Halten Sie also Ihre Fragen kurz und prägnant. Schließen Sie Fehlermeldungen und andere Ausgaben mit ein (und zwar wörtlich) und erläutern Sie kurz, was Sie schon selbst probiert haben. Nehmen Sie sich Zeit, um gute Fragen zu stellen. Sie erhöhen damit nicht nur die Wahrscheinlichkeit, eine hilfreiche Antwort zu erhalten, sondern können – falls das Forum öffentlich ist und durchsucht werden kann – damit auch anderen helfen, die vergleichbare Probleme haben.
Sie haben nun Ihren Werkzeugkasten aus Kapitel 1 deutlich erweitert und können sich schwierigeren und anspruchsvolleren Problemen auf der Kommandozeile zuwenden. Die folgenden Kapitel sind voller praktischer Beispiele für die Verwendung Ihrer neuen Befehle in allen möglichen Situationen.