Befehlsketten
Es gibt ein paar wunderbare Dinge, die in der Welt der grafischen Benutzeroberflächen fehlen, aber auf der Linux-Kommandozeile zum Standardrepertoire gehören. Ein Beispiel dafür sind Befehlsketten: dabei verknüpft man die Eingaben und Ausgaben verschiedener Programme so, dass sie zusammenarbeiten um etwas zu berechnen.
Ich möchte das an einer konkreten Befehlskette demonstrieren:
apt list --installed | cut -d/ -f1 | sort | uniq | fzf -e
Bevor wir die auseinandernehmen, gebe ich eine ganz knappe Einführung.
Ganz knappe Einführung
Die Arbeit mit der Kommandozeile (i.e. Terminal, i.e. Konsole) ist ein textbasiertes Ping-Pong-Spiel mit dem Computer: man setzt am sogenannten Prompt einen Textbefehl ab, dann macht der Computer irgendetwas damit und gibt ggf. eine textuelle Rückmeldung. Wenn er damit fertig ist, zeigt er wieder den Prompt an und wartet auf den nächsten Textbefehl.
Das ist eine textbasierte Alternative zur grafischen Interaktion mit dem Computer, bei der man hauptsächlich via Maus oder Touchpad den Cursor über den Bildschirm bewegt, visuelle Elemente bspw. mit einem Klick aktiviert, die Reaktion des Computers wahrnimmt und dann fortfährt. Nur Bei Bedarf führt man die Hand zur Tastatur und gibt Text ein.
Gegenüber der grafischen Benutzeroberfläche ist die Kommandozeile eine zugegeben etwas altbackene Form der Bedienung. Andererseits sind klassische GUIs mit Maus oder Touchpad auch nicht mehr die Norm, denn die meisten Bildschirme sind heute Touch-Bildschirme.
Verkettung mit dem Pipe-Operator
Ich möchte hier keine Einführung in die Kommandozeile geben, aber wenn du ein Grundverständnis dafür hast, wie man auf ihr Befehle eingibt und die Rückmeldung interpretiert, genügt das.
Befehle sind kleine Programme, die Datenströme verarbeiten. Ein Eingabestrom wird konsumiert und verarbeitet. Dabei werden vielleicht irgendwelche Nebeneffekte hervorgerufen (Dateien erzeugen oder löschen, Netzwerkanfragen absenden, Raketen starten, …). Schließlich wird ein Ausgabestrom befüllt.
Manche Programme sind nicht an einem Eingabestrom interessiert. Bspw. holt sich ls
über Systemaufrufe die Informationen über das aktuelle Verzeichnis und seine Inhalte. Die Auflistung, die ls
erzeugt, wird aber über einen Ausgabestrom weitergegeben.
Wenn du einen einzelnen Befehl absetzt, wird der Ausgabestrom so umgeleitet, dass die Ausgabe direkt unter dem abgesetzten Befehl erscheint. Bspw. gibst du ls
ein, bestätigst mit der Eingabetaste und erhältst dann direkt darunter die gewünschte Auflistung der Verzeichnisinhalte.
$ ls
'W6 Wertarbeit Einfädeln W6 N 1800, 1615, 1235⧸61 [WE0sv2XWF54].mkv'
'W6 Wertarbeit Nähmaschine N 1235⧸61 [GyE0OaKJe14].mkv'
'W6 Wertarbeit Nähmaschine N 1235⧸61 Jahr 2016 [pq_l9jE4vE4].webm'
W6-N-123561-Betriebsanleitung.pdf
$
Anderer Befehl, selbes Prinzip:
$ echo hallo
hallo
$
Allerdings kannst du mit dem Pipe-Operator |
den Ausgabestrom eines Programms zum Eingabestrom des nächsten Programms machen. So lässt sich bspw. die Ausgabe von echo hallo
mit dem Textprozessor awk
in Großschreibung überführen:
$ echo hallo | awk '{ print toupper($0) }'
HALLO
$
Das Beispiel ist trivial, aber vielleicht erkennt man daran schon ganz gut, welche Möglichkeiten sich auftun, wenn man ein kleines Repertpoire an solchen Bausteinen im Kopf hat, die sich gut miteinander kombinieren lassen.
Installierte Softwarepakte auflisten
Jetzt können wir uns der Befehlskette vom Anfang widmen.
apt list --installed | cut -d/ -f1 | sort | uniq | fzf -e
Der Befehl apt list --installed
druckt eine Liste aller installierten Softwarepakete aus, aber die Ausgabe ist sperrig und viel zu lang um daraus schlau zu werden.
$ apt list --installed
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
Listing...
abiword-common/jammy,jammy,now 3.0.5~dfsg-1 all [installed,automatic]
abiword-plugin-grammar/jammy,now 3.0.5~dfsg-1 amd64 [installed,automatic]
abiword/jammy,now 3.0.5~dfsg-1 amd64 [installed]
accountsservice/jammy-updates,jammy-security,now 22.07.5-2ubuntu1.5 amd64 [installed,automatic]
acl/jammy,now 2.3.1-1 amd64 [installed,automatic]
acpi-support/jammy,now 0.144 amd64 [installed]
acpid/jammy,now 1:2.0.33-1ubuntu1 amd64 [installed,automatic]
adduser/jammy,jammy,now 3.118ubuntu5 all [installed]
adwaita-icon-theme/jammy,jammy,now 41.0-1ubuntu1 all [installed,automatic]
...
... (Hier kommt noch seitenweise mehr Ausgabetext)
...
$
Ich bin pro Zeile nur an dem Namen des Paketes interessiert. Die Informationen nach dem Schrägstrich möchte ich verwerfen. Das geht mit cut -d/ -f1
, was so viel bedeutet wie “trenne die Zeilen am Schrägstrich auf und gib mir jeweils nur das erste Feld, also den Text vor dem Schrägstrich”:
$ apt list --installed | cut -d/ -f1
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
Listing...
abiword-common
abiword-plugin-grammar
abiword
accountsservice
acl
acpi-support
acpid
adduser
adwaita-icon-theme
...
... (Hier kommt noch seitenweise mehr Ausgabetext)
...
$
Das sieht schon viel besser aus, aber es ist noch nicht ideal: wenn ich die ungekürzte Ausgabe zeigen würde, könnten wir sehen, dass es eine Reihe von Paketen gibt, die mehrfach aufgelistet werden. An dem Ausschnitt sieht man das ganz gut:
...
libxmlb2
libxmu6
libxmuu1
libxpm4 <---
libxpm4 <---
libxrandr2 <---
libxrandr2 <---
libxrender1 <---
libxrender1 <---
libxres1
libxs-parse-keyword-perl
libxshmfence1 <---
libxshmfence1 <---
libxslt1.1 <---
libxslt1.1 <---
libxss1 <---
libxss1 <---
libxt6
libxtables12
libxtst6
libxv1 <---
libxv1 <---
libxvidcore4
libxvmc1
libxxf86dga1
libxxf86vm1 <---
libxxf86vm1 <---
libxxhash0
libyajl2
libyaml-0-2
...
Die Redundanz hängt vermutlich damit zusammen, dass in den Paketquellen aus guten Gründen manchmal mehrere Varianten eines Paketes verfügbar sind. Wir können die redundanten Zeilen entfernen mit uniq
. Das verarbeitet seine Eingabe zeilenweise und nimmt nur nicht-redundante Zeilen in die Ausgabe auf. Allerdings vergleicht uniq
nur benachbarte Zeilen auf Redundanz. Deswegen habe ich es mir zur Angewohnheit gemacht, uniq
immer zusammen mit sort
aufzurufen, um die Zeilen vorher alphabetisch zu sortieren, also : sort | uniq
. Hier ist das unnötig, aber ich folge trotzdem meiner Gewohnheit. Wenn wir die Befehlskette entsprechend ergänzen, sieht sie so aus:
apt list --installed | cut -d/ -f1 | sort | uniq
Die Ausgabe von apt list --installed
ist dann sauber auf die eigentlichen Paketnamen reduziert, garantiert alphabetisch sortiert und um Redundanzen bereinigt. Das ist sehr gut, aber die Liste ist immer noch viel zu lang für eine Bildschirmausgabe.
Hier kommt fzf
ins Spiel, der “general-purpose command-line fuzzy finder”. fzf
konsumiert seine Eingabe zeilenweise und präsentiert dem User eine Auflistung dieser Zeilen zusammen mit einem darunterliegenden Suchprompt um die Auflistung einzugrenzen.
Das Suchverhalten von fzf
ist standardmäßig nicht ganz nach meinem Geschmack: es sucht geordnete Teilmengen auf Zeichenebene. Beispielsweise findet es die Zeichenkette "Elefanten"
wenn ich nach "Ente"
suche (wegen Elefanten), aber wenn ich eine Ente suche möchte ich keine Elefanten haben. Wenn man fzf
mit der Flag -e
(für --exact
) aufruft, ändert sich das Suchverhalten so, dass man besser gezielt nach bestimmten Paketnamen suchen kann.
Damit ist die Befehlskette vollständig:
apt list --installed | cut -d/ -f1 | sort | uniq | fzf -e
Sie listet mir alle installierten Pakete auf und lässt mich interaktiv nach Paketnamen suchen. So kann ich für einzelne Pakete bequem prüfen, ob sie installiert sind.
Wenn ich --installed
weglasse, erhalte ich stattdessen eine Auflistung aller verfügbaren Pakete. Damit kann ich bequem prüfen, ob bestimmte Software dem Namen nach in den Paketquellen enthalten ist.
Fazit
Das wirkt bestimmt alles recht umständlich. Ich höre schon den Einwand, dass es völlig absurd ist, sich diese ganze Komplexität aufzuladen, anstatt einfach das GUI-Programm für die Paketverwaltung zu öffnen und sich darin die installierten Softwarepakete auflisten zu lassen. Das stimmt! Aber es ist nicht der entscheidende Punkt.
Irgendjemand hat dieses GUI-Programm für genau diesen Zweck geschrieben. Die Bausteine der Befehlskette sind aber generisch (abgesehen von apt
). Text manipulieren, Zeilen sortieren und filtern, Listen duchsuchen: das sind alles generische Operationen, die nichts mit Paketverwaltung zu tun haben, aber miteinander kombiniert werden können für diesen Anwendungsfall (und für viele andere).
Es gibt genug Anwendungsfälle, für die noch niemand ein dediziertes GUI-Programm geschrieben hat. Auf der Kommandozeile können wir uns dann trotzdem eine Lösung zusammenstricken; und wenn das einmal zur Gewohnheit geworden ist, wird es vielleicht auch zu einer Präferenz.
Dann ist es auch nicht mehr so umständlich wie es auf den ersten Blick scheint. Ich leite mir apt list --installed | cut -d/ -f1 | sort | uniq | fzf -e
nicht jedes Mal neu her, sondern ich gebe am Prompt meiner Fish-Shell nur apt
ein und dann erscheint schon die ganze Befehlskette als Vorschlag, den ich mit einem Tastendruck akzeptiere.