Für die meisten Leute mag die Shell total sinnlos und umständlich erscheinen. Die Syntax sieht absolut unverständlich aus. Man braucht für einfache Dinge komplizierte Konstrukte. Teilweise sind Leerzeichen zwischen Befehlen von Bedeutung. Alles in allem also absolut nicht zu gebrauchen.
Ich finde es genial! Das Prinzip dahinter ist total simpel, wenn auch die Anwendung etwas kryptisch erscheinen mag.
Wenn ich ein kleines Script schreibe mache ich im Grunde nichts anders als Shell Befehle aneinander zu reihen bzw. ineinander zu verschachteln. So zu sagen Komposition in ihrer Reinform (zumindest vom Prinzip, aber dazu später mehr).
Ein Shell Befehl ist vom Prinzip immer gleich aufgebaut:
Allgemein: Befehlsname Optionen Beispiel: Concatenate File1 File2 Umsetzung: cat old.log new.log
Im Grunde wird hier nichts anderes gemacht als die Datei old.log ausgegeben und anschließend die Datei new.log. Also beide Dateien aneinander gehängt (konkateniert). Dazu wird das Programm cat verwendet welches unter /bin/cat
abgespeichert ist. Als Parameter werden die beiden Dateien angegeben die ausgegeben werden sollen.
Will ich nun ein neues Programm schreiben und ebenso aufrufen kann ich es einfach ins /bin/
oder /usr/bin/
Verzeichnis speichern.
Hier könnte der Beitrag zu Ende sein, aber ich will an Beispielen erklären warum das so genial ist.
if Befehl
if true; then echo "hallo" fi
Dieser Codeblock macht zugegebenermaßen nicht viel Sinn, er gibt einfach nur hallo
aus. Aber die Funktionsweise ist interessant. if
ist ein vordefiniertes Kommando. Deshalb schrieb ich oben auch das die Komposition einige Lücken hat. if
ruft das Programm true
auf welches unter /bin/true
liegt. true
macht nichts anderes als ein erfolgreichen exit
-Status zurück zu geben. Das ;
danach ist wichtig da es einfach sagt der Befehl if true
ist abgeschlossen. Alternativ könnte man auch schreiben:
if true then echo "hallo" fi
Anschließend kommt ein then
welches aufgerufen wird wenn die if
Bedingung korrekt war. then
ist ebenfalls wie das if
und das fi
ein eingebautes Keyword. echo
gibt einfach nur hallo
aus.
Exit States
Ein kurzer Exkurs zu exit-States:
Jedes Programm gibt ein exit Status zurück. Wenn alles korrekt verlaufen ist wird eine 0 zurück gegeben. Ansonsten ein beliebiger Wert zwischen zwischen 1 und 255. Das ist dann ein Fehlercode der ausgewertet werden kann. Den exit
Status des letzten Programmaufrufs wird in der Variable $?
gespeichert.
Ich mache einen ping
auf einem Computernamen und schaue so ob dieser im Netzwerk erreichbar ist. localhost
ist mein eigener Computer. Der ist also immer erreichbar. -c 1
gibt einfach nur an es soll nur ein einzelner ping
gemacht werden und nicht mehrere.
ping -c 1 localhost echo $?
Liefert eine 0 was so viel heißt wie: Jap, Ping war erfolgreich!
ping -c 1 asdf echo $?
Liefert eine 2 was so viel heißt wie: Konnte asdf nicht finden!
true echo $?
Ist also immer 0 wohingegen false
(was ebenso wie true
auch nur ein Programm ist) immer 1 zurück gibt.
if
macht im Grunde nichts anderes als diese exit
-States zu überprüfen. Das ist sehr komfortabel weil man kann so statt true
oder false
jedes beliebige Programm einfügen.
Man könnte schauen ob der Computer www.google.de
erreichbar ist und wenn ja einfach online
ausgeben.
$ if ping -c 1 www.google.de > /dev/null; then echo "online" fi
>
leitet die Ausgabe des Programms um. Wen man also cat old.log new.log > both.log
schreibt, enthält die Datei both.log
die Dateien old.log
und new.log
. /dev/null
ist so was wie das Nirvana.
test Befehl
Ein anderes Programm welches oft genutzt wird ist test
bzw. das (fast) synonyme Alias [
.
Einfach Prüfen ob die Variable $DEBUG
größer 1 ist und wenn ja "Debug on" ausgeben.
if [ $DEBUG -gt 1 ]; then echo "Debug on" fi
[
ist ein Programm Aufruf und -gt
ist nur ein Parameter der so viel sagt wie: Prüfe ob der Wert vorher größer (greater) als der wert danach ist. Das Programm [
prüft alle Ausdrücke bis er zu dem Parameter ]
kommt.
Also noch einmal vereinfacht:
[ $DEBUG -gt 1 ] ^ ^ ^ ^ ^ Programmname 4 Parameter
Man sieht hier schon. Die wirklich einfachen Befehle können richtig ineinander verschachtelt werden und werden so noch mächtiger.
if [ \( $CHECKOUT -eq 1 \) -o \( $IMPORT -eq 1 \) ]
-eq
steht für equal also gleich und das -o
ist ein oder. Das \
vor den Klammern Maskiert die Klammern nur, da diese eigentlich von der Shell schon vordefiniert sind.
Arithmetik
Auch einfaches Rechnen ist interessant. Man kann nicht einfach wie in anderen Programmiersprachen I = 1 + 1
schreiben. Die Rechnung ist wieder ein Befehlsaufruf:
I=`expr 1 + 1`
`...`
ist ein Unterprogrammaufruf. Also würde ich echo pwd
angeben würde ich pwd
zurück bekommen. Gebe ich echo `pwd`
an bekomme ich das aktuelle Arbeitsverzeichnis (Print Working Directory), da erst der Befehl pwd
ausgeführt wird und dann echo
.
Für viele ist das unverständlich, aber wenn man sich wieder in Erinnerung ruft wie ein Befehl aufgebaut ist wird es klar. Zuerst kommt der Befehlsname und dann die Parameter. Nutzt man also die Normale Infix Notation 1 + 1
kann es nicht funktionieren. Man müsste schreiben + 1 1
. +
Wäre also der Programmname und 1
und 1
die Parameter. expr
ist etwas Allgemeiner gefasst da der Befehl auch Subtrahieren etc. könnte.
for Schleifen
Aus der normalen Programmiersprache kennt man das ja in der Form:
for (int i = 0; i < 10; i++)
Die Shell unterscheidet sich hier Grundlegend. Es gibt nur for i in $ARRAY; do .. ; done
. Also es kann nur alle Elemente in einem Array durchgehen und nicht etwa alle Zahlen von 1 bis 10. Und genau das macht es so mächtig!
Ich kann z.B. schreiben:
PCS="lola redbull flens jever becks wodka coke pommes hotdog kebap bigmac twix" for PC in `echo $PCS`; do echo $PC done
PCS ergibt ein String welcher durch das echo ausgegeben wird so als würde ich alle Wörter einzeln nach das in schreiben. So bekomme ich ein Array was ich anschließend mit der for
-Schleife durchlaufen kann. Man sieht schon, ich kann für den echo
Befehl auch alles andere einsetzen was mir ein Array zurück gibt. Wobei Arrays einfach nur eine Aneinanderreihung von Wörtern sind: array=( zero one two three four five )
Das mag erst einmal total umständlich erscheinen da man so nicht wie gewohnt Zahlen durchlaufen kann. Man muss aber einfach nur den richtigen Befehl einsetzen:
for NUMBER in `seq 4 5`; do echo $NUMBER done
seq
gibt eine Zahlenfolge zurück. In diesem Fall startet die Folge bei 4 und endet mit 5 so das als Ausgabe die 4 und 5 erscheint.
Gibt man seq
jetzt noch eine Formatierung mit kann man das ganze noch verändern:
for i in `seq -f"%03g" 1 10`; do echo $i done
Gibt alle Zahlen zwischen 1
und 10
mit führenden Nullen aus: 001, 002, 003, ... 010
Zusammenfassung
Man sieht schon, durch die richtige Kombination können die simplen Befehle viel bewirken. Leider sind nicht alle Befehle Programme. Vor allem Grundlegende Befehle wie if, while, case, else, for, then etc. sind bereits vordefiniert. Ich habe dummerweise keine Ahnung warum. Meine einzige Vermutung wäre das dann die Aufrufe zu Komplex werden. Ich muss ja alles als Postfix definieren und das kann etwas unübersichtlich werden. Beispielsweise kann ich dann nicht mehr schreiben cat test.log > test2.log
da das >
am Anfang stehen müsste. Also dann: > `cat test.log` test2.log
.
Eigentlich sollte dieser Eintrag nicht wie ein Shell Tutorial aussehen sondern erklären warum Shellbefehle nicht umständlich sondern anpassbar sind. Wenn ich mir die Länge des Beitrags anschaue wurde es an manchen Stellen etwas ausführlich. Ich hoffe aber trotzdem die Botschaft kam durch. Durch Schachtelung kann man erstaunliche Effekte erzielen. Außerdem ist es natürlich vorteilhaft das man jedes Script das man schreibt wieder in anderen Scripten verwenden kann, indem man es einfach wie ein Befehl ausführt.
2 Kommentare:
Toller Artikel!
Vielen Dank!
Ich bin nicht ganz zufrieden mit dem Artikel, da er viel länger wurde als erwartet und ich trotzdem nicht weiß ob meine Message rüber kam.
Deswegen hier noch mal kurz und knapp:
Das Tolle ist ich brauche nicht wie in anderen Programmiersprachen dutzende von Befehlen und Libraries sondern ich habe nur einen kleinen Interpreter der im Grunde nur Dateien mit Parameter aufrufen müsste (in Wirklichkeit macht der Interpreter noch viel mehr). Das ganze ist total simpel zu erweitern da ich einfach nur eine neue Datei anlegen muss. Und absolut Sprachunabhänig. Ich kann sowohl ein Bashscript, ein Pythonscript, ein Rubyscript, ein C++ Programm ein Java Programm oder was auch immer miteinander verknüpfen. Solange sich alle an dieses total simple Interface halten (Parameter, Exit-Code, und noch ein paar die ich nicht explizit erwähnt habe).
Kommentar veröffentlichen