KAPITEL 5

Das Arsenal erweitern

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.

Text erzeugen

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

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

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.

Klammererweiterung (eine Shell-Eigenschaft)

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

image

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

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

/etc/systemd/timesyncd.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:

  1. Konstruieren Sie einen find-Befehl und lassen Sie -print weg.
  2. Hängen Sie ein -exec an, gefolgt von dem Befehl, der ausgeführt werden soll. Benutzen Sie den Ausdruck {}, um anzudeuten, wo der Dateipfad in dem Befehl auftauchen soll.
  3. Beenden Sie das mit einem in Anführungszeichen stehenden oder anderweitig geschützten Semikolon, wie etwa ";" oder \;.

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

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

Text isolieren

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.

image

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.

grep: Ein tieferer Einblick

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 tail-Befehl

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

image

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 {print}

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.

Text kombinieren

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:

$ echo efficient linux in $HOME

efficient linux in /home/smith

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

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 paste-Befehl

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

Der diff-Befehl

diff vergleicht zwei Dateien Zeile für Zeile und gibt einen Bericht über deren Unterschiede aus:

$ cat file1

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.

Text transformieren

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.

Der tr-Befehl

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 rev-Befehl

Der Befehl rev kehrt die Zeichen jeder Eingabezeile um:6

$ echo Efficient Linux! | rev

!xuniL tneiciffE

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

Die Befehle awk und sed

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:

Die awk-Grundlagen

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:

Das Wort BEGIN

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

image

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.

Den Duplikate-Detektor verbessern

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

Die sed-Grundlagen

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

image

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

image

Substitution und Schrägstriche

Die Schrägstriche in einer Substitution lassen sich durch ein anderes gewünschtes Zeichen ersetzen. Das ist ganz hilfreich, wenn ein regulärer Ausdruck selbst Schrägstriche enthält (die ansonsten anderweitig geschützt werden müssten). Diese drei sed-Skripte sind äquivalent:

s/one/two/ s_one_two_ s@one@two@

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

Teilausdrücke mit sed erfassen

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«.

Weiter zu einem noch größeren Werkzeugkasten

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.

Zusammenfassung

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.