Bei unserem 11ten Meetup haben wir uns mit den gängigsten Kommandozeilen-Befehlen auseinandergesetzt. katrin hat uns dafür einen Input geliefert und auch ein paar Kommandozeilenschmankerl herausgesucht. Dabei konnten wir die vorgestellten Befehle alle gleich auch selbst ausprobieren. In diesem Blog-Beitrag findet ihr die von uns ausprobierten Befehle samt Erklärung dazu, was sie machen.
Zu Beginn stand aber gleich einmal die Frage im Raum was denn eigentlich der Unterschied zwischen der shell, dem Terminal, der Konsole und der Kommandozeile ist. Praktisch gesehen verwenden wir diese Begriffe meist synonym. Und das funktioniert auch ganz gut. Mensch kann hier aber auch genauer differenzieren und in die Geschichte der Computerentwicklung abtauchen. Das sparen wir hier im Detail mal aus, verweisen aber auf ein paar nicht unspannende Wikipedia-Artikel:
- https://de.wikipedia.org/wiki/Kommandozeileninterpreter
- https://de.wikipedia.org/wiki/Shell_(Betriebssystem)
- https://de.wikipedia.org/wiki/Terminal_(Computer)
An dieser Stelle ist uns nur wichtig zu sagen, dass wir mit dem Computer bzw. dem darauf laufenden Betriebssystem durch Eingabe von Befehls- bzw. Kommandozeilen interagieren können. Dazu benötigen wir ein Programm, das diese eingegebenen Kommandozeilen interpretiert. Das ist dann der Kommandozeileninterpreter, bzw. die Shell. Das Terminal bzw. die Konsole waren historisch einfach Geräte, die eine Verbindung mit einem Großrechner herstellten um überhaupt erst mal Ein- und Ausgabe zu ermöglichen, die Shell lief dabei direkt am Großrechner.
Beispielbefehle
Bevor wir hier gleich mit den Befehlen loslegen, noch eine kurze Erklärung wie denn solche Befehle bzw. ganze Befehlszeilen oder eben Kommandozeilen aufgebaut sind. Im Prinzip ist von der jeweiligen Shell und ihrer Syntax abhängt. Bei den meisten gängigen Shells unter Linux gibt es aber ein Muster wie so eine Kommandozeile ausschaut. Die einfachste Variante ist ein einzelner Befehle, oder irgendwelche weiteren Optionen:
bash befehlsname
Der befehlsname kann nun der Name von einem Programm sein, das ich ausführen möchte, oder auch ein eingbauter Shell-Befehl sein. Oft wollten wir aber dem Programm oder Kommando noch weitere Optionen mitgeben. Das können wir wie in folgendem Beispiel über flags und Argumente machen:
befehlsname -a -b -c --lange-option argument1 argument2
Hier sind -a
, -b
und -c
sogenannte flags, die den Befehl modifizieren. --lange-option
ist in dieser Variante im Prinzip auch ein flag, nur eben für uns besser les- und merkbar, und wird dann oft als long option bezeichnet. argument1
und argument2
sind dann zwei allgemeine Argumente, mit denen der Befehl etwas tun soll. Was all die flags, Optionen und Argumente jeweils tun, hängt vom Befehl ab.
Wir können uns direkt in der Shell eine Liste mit flags zum jeweiligen Befehl mit man befehlsname
ausgeben lassen, das ist dann die sogenannte manual page. Oder wir suchen mit der Suchmaschine unserer Wahl im Web danach, oder schauen zum feminist linux meetup 😉
Nun aber genug Theorie, los gehts mit den Befehlen:
Navigieren & Explorieren
Mit cd
(change directory) können wir in ein untergeordnetes Verzeichnis wechseln:
cd testFolder1
Ohne zusätzlichen Parameter wechselt man in das home directory, ganz unabhängig davon, wo wir uns gerade eben befinden:
cd
Mit -
als Parameter wechselt man in das zuletzt besuchte Verzeichnis:
cd -
Mit ..
wechselt man in das übergeordnete Verzeichnis:
cd ..
ls
gibt eine Liste der Files und Verzeichnissen im aktuellen Verzeichnis aus. Mit verschiedenen flags können wir die Liste anpassen:
-l
(long listing format) zeigt Berechtigungen, Owner, Group, Größe, Änderungsdatum-a
bzw.--all
zeigt auch versteckte Dateien-t
nach Änderungsdatum sortiert (neuestes zuerst)
flags können je nach Fragestellung beliebig kombiniert werden:
ls -l ls -la ls -lat ls -lt
Es gibt noch viel mehr flags! Wir können uns in der Shell eine auch eine Liste mit flags zu einem bestimmten Befehl mit man
(manual) ausgeben lassen.
man ls
Erstellen, verändern, kopieren
mkdir
(make directory) erzeugt einen neuen Ordner im aktuellen Verzeichnis, falls es diesen noch nicht gibt. Der Name des Ordners muss angegeben werden:
mkdir folder1
Mit rm
(remove) kann eine Datei im aktuellen Verzeichnis gelöscht werden:
rm file1
Mit rmdir
(remove directory) kann ein Verzeichnis gelöscht werden, aber nur wenn es leer ist:
rmdir folder1
Wenn wir ein Verzichnis mit allen Unterverzeichnissen und Dateien löschen wollen, verwenden wir rm -r
(recursive). Aber Vorsicht! Das kann nicht rückgängig gemacht werden und es gibt keine Warnung vom System!
rm -r folder1
Um eine Datei zu kopieren verwenden wir cp
(copy). Dazu geben wir die zu kopierende Datei und die Zieldatei an.
cp example.txt /home/user/tmp.txt
Es wurde hier die example.txt
Datei aus dem Verzeichnis in dem wir uns gerade befinden kopiert. Falls wir keine Datei aus dem aktuellen Verzeichnis kopieren wollen, müssen wir den absoluten Pfad für die Datei angeben:
cp /home/user/example.txt /home/user/tmp.txt
Anstatt der Zieldatei, kann auch ein Zielordner angegeben werden. Oder anders gesprochen: falls das angegebene Ziel bereits als Ordner exisitert, wird die zu kopierende Datei in diesen Ordner kopiert (und behält dabei ihren ursprünglichen Namen):
# /home/user/tmp ist ein bereits bestehender Ordner cp example.txt /home/user/tmp # Nun sollte sich in diesem Ordner eine example.txt befinden ls -l /home/user/tmp/example.txt
Mit der recursive flag -r
kann cp
auch auf ganze Verzeichnisse angewendet werden und es wird der gesamte Inhalt mitkopiert.
cp -r Beispiel_Verzeichnis Kopie_von_Beispiel_Verzeichnis
Dateien verschieben funktioniert ähnlich. Mit mv
(move) wird eine Datei (oder auch ein Ordner) verschoben.
# Wir verschieben die example.txt Datei nach tmp.txt # Das kommt einer einfachen Umbenennung der Datei gleich mv example.txt tmp.txt # Nun verschieben wir die Datei tmp.txt in den Folder tmp mv tmp.txt tmp ls -l tmp/tmp.txt # Jetzt bennen wir den Folder tmp in tmpFolder um mv tmp tmpFolder
Schätze aus der Historienkisten holen
Die Shell schreibt für uns auch eine Geschichte, in dem sie alle Befehle die wir ausführen für eine gewisse Zeit aufbewahrt. Welche Befehle wir in der Vergangenheit ausgeführt haben können wir uns zum Beispiel mit folgendem einfachen Befehl ansehen:
history
Wir können die history aber auch interaktiv durchsuchen um Befehle direkt in unsere Kommandozeile zu holen. Dazu drücken wir zuerst Strg + R
und tippen dann wonach wir suchen wollen. Es wird dann der erste Befehl angezeigt, der in der history rückwärts vorkommt, wo die gesuchte Textstelle gefunden wird. Also wenn ich sudo
eintippe, wird der letzte Befehl angezeigt den ich verwendet habe, in dem sudo
vorkommt. Wenn wir nun nochmal Strg + R
drücken, dann wird der nächstältere Befehl angezeigt, in dem sudo
vorkommt. Und so weiter, also mit jedem weiteren Strg + R
noch ein älterer Befehl, solange bis keine weiteren mit diesem Suchbegriff gefunden werden. Wenn ich einen Befehl gefunden habe, den ich wieder verwenden (oder verändern) möchte, habe ich folgende Möglichkeiten:
- Ich drücke gleich die Enter-Taste und der Befehl wird so wie er ist neu ausgeführt
- Ich verwenden die
Ende
– oderPos1
-Taste um ans Ende oder den Anfang des Behfels zu springen und kann den Befehl dann noch verändern, befor ich ihn tatsächlich ausführe
Wenn ich mit meiner Suche gar nicht zufrieden bin und lieber abbrechen möchte, kann ich das mit Strg -C
jederzeit tun.
Nun bietet die Shell aber auch noch viele history expansion und history substitution Möglichkeiten, die auch sehr praktisch sein können. Eine davon ist !!
, was einfach für den zuletzt ausgeführten Befehl steht. Der kann so in einen neuen Befehl eingebaut werden. Besonders praktisch kann das mit komplizierteren Befehlen sein, für die ich eigentlich sudo
Rechte benötige, was mir aber erst auffällt, nachdem ich den Befehl schon ausgeführt habe. Anstatt also den Befehl erst mit der Pfeiltaste nach oben nochmal anzusteuern und dann an den Anfang zu gehen um noch sudo
hinzuzufügen können wir auch einfach folgendes tun:
sudo !!
So wird einfach die zuletzt ausgeführte Kommandozeile an ein sudo
angehängt und damit der letzte Befehl mit Administrator*innenrechten nochmal ausgeführt.
Das ist aber auch mit dem vor- oder vorvorletzten Befehl möglich. So gibt es eine generische Syntax um Befehle in die Vergangenheit zu referenzieren: !-n
. Dabei gibt n
die Nummer an, also 1 für den letzten Befehl, 2 für den vorletzten, und so weiter. Wenn ich also den vorletzten Befehl nochmal mit asführen möchte, kann ich folgendes machen:
!-2
Wie oben auch, könnte ich auch einen anderen Befehl (z.B. ein sudo
) davor geben. Das !!
oben ist somit gleich wie ein !-1
.
Viele umfassendere Möglichkeiten der history expansion und subsitution finden sich in der man history
erklärt. Wie so oft ists aber gar nicht so zielführend zu versuchen sich das alles zu merken. Ab und an kann ein Blick dort hinein aber neue praktische Ideen und Hilfestellungen mit sich bringen.
Ein besonderes Schmankerl zum Abschluss, den uns katrin aus dem Web gesucht hat (die Quelle für diesen Fund scheint leider nicht mehr aktiv zu sein), ist folgende Aneinanderreihung von Befehlen, die uns aus der history die Top 10 unserer meistbenutzten Befehle inklusive Prozentangaben raussucht:
history | awk '{CMD[$2]++;count++;}END { for (a in CMD)print CMD[a] " " CMD[a]/count*100 "% " a;}' | grep -v "./" | column -c3 -s " " -t | sort -nr | nl | head -n10
So, das wars dann auch mit unserer basic shell intro. Das Schmankerl grad eben deutet aber darauf hin, dass sich noch viel speziellere Dinge mit der Shell machen lassen. Das kann aber auch schnell ganz undurchsichtig und überfordernd werden, wenn eins gerade erst mal anfängt sich mit der Shell auseinanderzusetzen. Nachfolgend geben wir daher noch ein Beispiel, wie mensch an so etwas rangehen kann, für alle die jetzt noch Lust auf mehr haben.
Sollte dein Kopf aber gerade schon vor Befehlswirrwarr brummen, lass es doch vielleicht erstmal sickern und schau ein ander mal wieder vorbei. Der Blogartikel bleibt auf jeden Fall hier und wartet auf dich, wann auch immer du zu Besuch kommen magst.
Komplexes Anwendungsbeispiel
Mit einzelnen Shell-Befehlen kann oft schon ganz viel gemacht werden. Die wirklich magisch mächtig wirkenden Dinge werden aber oft erreicht, indem verschiedene Shell-Befehle kombiniert werden. Dazu ist vor allem ein Konzept wichtig, die Pipe: „|
„. Das Zeichen wird deswegen Pipe genannt, weil es verwendet werden kann um die Ausgabe von einem Befehl als Eingabe in den nächsten Befehl hineinzufüttern. Zwei Befehle werden also quasi wie über ein Rohr miteinander verbunden.
Das ist mitunter im History-Schmankerl oben viel verwendet worden. Hier wollen wir nun versuchen anhand einer konkreten Frage-/Aufgabenstellung so eine Kombination zu generieren. Und zwar lautet die Frage: Wie kann ich mit einem Befehl eine Kopie des neuesten Files in einem Folder generieren?
Der Kopiervorgang wäre ja einfach, wenn wir sagen könnten: cp neuestes-file.dat kopie.dat
Wir müssen also versuchen in der oberen Zeile neuestes-file.dat
durch den Dateinamen dieses neuesten Files zu ersetzen. Hier kommt noch ein anderes wichtiges Konzept ins Spiel, die command substitution: „$()
„. Wenn ich irgendwo in einer Kommandozeile ein $(befehlszeile)
verwende, dann wird die befehlszeile
ausgeführt und deren Ausgabe an die Stelle in meiner eigentlichen Kommandozeile gesetzt, wo ich $(befehlszeile)
stehen habe.
Das ändert unseren obigen Befehl in cp $(…) kopie.dat
ab. Nur dass wir anstatt …
nun eine Kommandozeile finden müssen, die uns den Dateinamen des zuletzt geänderten Files ausspuckt.
Schauen wir uns also erstmal an, wie wir alle Files im Folder nach Datum sortiert auflisten können:
ls -lt
Die Ausgabe von dem Befehl schaut dann z.B. so aus:
total 24 -rw-r--r-- 1 jackie jackie 82 Jän 22 2021 Dockerfile -rw-rw-r-- 1 jackie jackie 770 Jän 6 2021 snippet.py -rw-rw-r-- 1 jackie jackie 426 Sep 26 2020 backgroundtest.css -rw-rw-r-- 1 jackie jackie 284 Sep 26 2020 backgroundtest.html -rw-r--r-- 1 jackie jackie 7739 Mär 9 2020 debug-gpu-hangs.txt
Wir bekommen also eine Liste an Dateien, wo die erste davon die jüngste, also zuletzt veränderte ist. Wir interessieren uns also nur für die zweite Zeile in dieser Ausgabe. Wir können hier mit den Befehlen head
und tail
arbeiten. Füttern wir also die Ausgabe in einen head
Befehl um uns erstmal nur die ersten beiden Zeilen ausgeben zu lassen:
ls -lt | head -n2
Das führt zu folgender Ausgabe:
total 24 -rw-r--r-- 1 jackie jackie 82 Jän 22 2021 Dockerfile
Wir können diese Ausgabe nun weiter in einen tail
Befehl füttern, um uns nur die letzte Zeile ausgeben zu lassen:
ls -lt | head -n2 | tail -n1
Nun sind wir bei folgender Ausgabe gelandet:
-rw-r--r-- 1 jackie jackie 82 Jän 22 2021 Dockerfile
Davon interessiert uns nun aber nur das letzte Wort, also der Dateiname, in diesem Fall Dockerfile
. Hier können wir den Befehl awk
verwenden, um die letzte Spalte in dieser Zeile herauszufiltern. Nebenbemerkung: awk
ist eigentlich ein Interpreter für eine eigene Sprache, die AWK programming language, die sich besonders für „pattern scanning and processing“ eignet. Aber wir müssen hier gar nicht erst noch eine eigene Sprache lernen, weil das Web genügend praktische Beispiele zur Verfügung hat. Nach etwas Suche lässt sich daher schnell rausfinden, dass ich folgenden Befehl verwenden kann um die 9te Spalte aus obiger Textzeile zu filtern: awk '{ print $9 }'
. Füttern wir also die obere Ausgabe in diesen Befehl hinein:
ls -lt | head -n2 | tail -n1 | awk '{ print $9 }'
Die Ausgabe schaut dann ganz nachdem aus, was wir in unserem eigentliche copy Befehl oben verwenden möchten:
Dockerfile
Fügen wir also nun diesen Befehl in unseren urpsrünglichen Befehl cp $(…) kopie.dat
ein.
cp $(ls -lt | head -n2 | tail -n1 | awk '{ print $9 }') kopie.dat
Die Ausgabe von ls -lt
ist nun also um eine neuere Kopie des Dockerfile ergänzt:
total 28 -rw-r--r-- 1 jackie jackie 82 Okt 23 09:38 kopie.dat -rw-r--r-- 1 jackie jackie 82 Jän 22 2021 Dockerfile -rw-rw-r-- 1 jackie jackie 770 Jän 6 2021 snippet.py -rw-rw-r-- 1 jackie jackie 426 Sep 26 2020 backgroundtest.css -rw-rw-r-- 1 jackie jackie 284 Sep 26 2020 backgroundtest.html -rw-r--r-- 1 jackie jackie 7739 Mär 9 2020 debug-gpu-hangs.txt
Wunderbar! Nun gibt es aber (mindestens) einen Haken. Der ganze Befehl funktioniert zum Beispiel nicht mehr, wenn das neueste „file“ in der Auflistung ein Verzeichnis ist. Wir müssten also eine Dateiauflistung finden, die Verzeichnisse ausschließt. Hier gibt es wieder unterschiedliche Ansätze. Ich könnte z.B. vor der Weiterverarbeitung der Liste alle Zeilen herausfiltern, die mit einem d
beginnen. Oder ich verwende anstatt ls
den Befehl find
um nur Dateien vom Typ „file“ zu finden. Und wie so oft gibt es wahrscheinlich noch mehrere Möglichkeiten, den Befehl noch besser zu machen. Dazu muss ich aber nicht schon jeglisches shell-fu beherrschen und einfach nur fest genug nachdenken. In der Regel ist es zielführender eine andere Person oder das Web zu befragen. Eine Suche in DuckDuckGo nach bash ls ignore directories gibt als zweiten Treffer z.B. einen Unix Stackexchange thread mit dem Titel How to list files without directories, and filter by name (ls options) zurück. Da hat eine Person ein ähnliches Problem und es findet sich schon eine Antwort, wie das mit find
gelöst werden kann.
Wenn ich aber in unserem Beispiel davon ausgehen kann, dass in meinem Folder nur Dateien sein werden, und keine Subfolder, dann reicht mit der Befehl oben ja auch und ich kann mir die Perfektionierung vorerst auch sparen. Wobei auch hier hilft der Austausch mit anderen Personen, weils oft noch einfacher geht. So hat z.B. bei der Erstellung des Artikels lorb über das Beispiel drübergeschaut und geschrieben, dass ls
ja schon Optionen hat, mit denen wir uns die Verwendung von tail
und awk
gleich sparen können:
cp $(ls -tw1 | head -n1) kopie.dat
Das wars für jetzt. Hoffentlich konnten wir einen guten Vorgeschmack darauf machen, was mit der Shell alles möglich ist. Wichtig war es uns aber auch zu zeigen, dass so kreative Lösungen nicht einfach aus dem Nichts oder irgendeiner genialen Regung entstehen, sondern sich durch Erfahrung und Verbesserung nach und nach ergeben können.
Dieser Artikel wurden von kathi, jaz und jackie erarbeitet, und mit Feedback aus unserer Community angereichert