Bitte Beachten: Dieser Blog wird hier nicht weiter geführt! Kommentare und neue Blogposts gibt es unter http://feitel.indeedgeek.de. Dort bitte auch neue Kommentare zu diesen Beiträge erstellen.

Donnerstag, 29. November 2007

Faszination Shell

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.

Beispiel:

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.

Beispiel:

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

Beispiel:

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:

Aaron hat gesagt…

Toller Artikel!

nougad hat gesagt…

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