Shell Programmierung Was ist die Shell überhaupt? Zunächst ein Kommandozeileninterpreter, eine Schnittstelle zwischen Benutzer und Betriebssystem. Aber sie ist mehr! Wie wir später sehen werden ist sie ein Interpreter für eine vollständige Skriptsprache Wozu benötigt man die Shell? Häufig schneller (tippen vs. mit der Maus durch Menüs) Manches zu komplex für graphische Interfaces Manchmal einzige Verfügbare Schnittstelle (Supercomputer, Cluster, etc.) Stärken: Aktionen im Filesystem Umgang mit Prozessen (weniger relevant -> Systemadminstration) Bearbeiten von Textdateien Wenn die gleiche Aktion oft gemacht werden muss Wenn etwas mit vielen Dateien gemacht werden muss Es gibt viele Shells: sh, ksh, csh, tcsh, bash, zsh usw. Man kann sie in zwei Klassen unterteilen: sh-artige: sh, ksh, bash, zsh... csh-artig: csh, tcsh Zum Programmieren eignen sich aus verschiedenen Gründen die sh artigen Shells besser. U.a. weil die Grundfunktionalität in einem Standard (Posix) festgelegt wurde, und damit Portabilität gewährleistet ist. Im folgenden werden wir daher die wohl verbreitetste Shell, die Bash benutzen. Dabei beschränken wir uns aber trotzdem auf im Standard festgelegte Kommandos und verzichten (hoffentlich) auf bash-eigene Erweiterungen (bashism). Nebenberkung: Auch wenn der Name danach klingt, die ssh ist keine Shell im eigentlichen Sinn.
Interaktiv: Navigieren: Üblicherweise EMACS-Mode, damit die gleichen Steuerkommandos wie der Editor. (Bei vielen Tools zu finden). CTRL-E CTRL-A Ende/ Anfang der Zeile CTRL-F CTRL-B Vorwärts/ Zurück CTRL-K CTRL-Y Löschen/Einfügen usw.... Komplettierung: TAB History: (programmierbar und teilweise sehr intelligent) Eingegeben Kommandos werden behalten und können so leicht wieder benutzt werden: history / bzw. CTRL-p / CTRL-n CTRL-r / CTRL-s Pattern-Matching: Blättern vorwärts / rückwärts (previous / next) Rückwärts- / Vorwärtssuche Nicht immer will man alle Dateien einzeln eingegeben, es gibt auch die Möglichkeit mit besonderen Platzhaltern Suchmuster zu erstellen, die von der Shell dann durch alle Dateien ersetzt werden die dieses Muster erfüllen: * Jeder String inkl. Null? Ein einzelnes Zeichen [0-9],[a-z], etc. Braces: Bereiche von Zeichen In geschweiften Klammern eingeschlossene Liste wird nach einander eingesetzt. a.{foo,bar}
Quoting: Wie gesehen haben einige Zeichen eine besondere Bedeutung und werden durch die Shell ersetzt. Will man diese Zeichen aber z. B. ausgeben, so ist so genanntes Quoting nötig: Voranstellen von \ Einschließen in " " oder ' ' Die Zeichen zum quoten müssen natürlich auch gequotet werden, z. B. \\ oder '"' Aliases: Zur Vereinfachung der täglichen Arbeit ist es möglich eigene Befehle zu definieren: alias ll='ls -l' alias gibt alle Definitionen aus unalias hebt sie auf Umgebungsvariablen: Die Konfiguration des Verhaltens der Shell wird durch das setzen sogenannter Umgebungsvariablen erreicht. Beispiel: PATH ist der Suchpfad für ausführbare Dateien ändern kann man diese z.b. export PATH=${PATH}:~/bin.bashrc: Um Definitionen von Aliasen und der Konfiguration durch Umgebungsvariablen nicht immer wiederholen zu müssen, kann die Datei.bashrc genutzt werden.
Shell Programmierung Vollständig Programmiersprache Kommentare: # Alles dahinter wird ignoriert Ausgabe: echo a b c für zeilenweise Ausgabe Variablen: VAR=var echo $VAR echo ${VAR}iable echo gibt Zeile aus, aber vorher ersetzt die Shell die Variablen -> Quoting echo -e interpretiert Backslash Escapes wie \n etc. $( ) Zuweisung von Rückgabe eines Befehls: VAR=$( Kommando ) Es geht los: for i in *.JPG; do mv $i ${i/%.jpg/%.jpg}; done ; oder CR trennen verschiedene Befehle for Schleife: for VAR in LIST; do Befehl1 $VAR Befehl2... done while und until-schleifen wie in anderen Programmiersprachen gibt es natuerlich auch.
Parameter-Expansion: Simpelste Form: ${} viele mehr (Fortgeschritten -> man-page) z.b. ${i/muster1/muster2} ersetzt in i MUSTER1 durch MUSTER2 ${i/%muster1/muster2} ersetzt in Werten die auf MUSTER1 enden MUSTER1 durch MUSTER2
Shell-Programme an Hand von zwei Beispielen. Das genaue Problem ist sehr verschieden, das Grundproblem aber i.d.r. identisch, es sind viele Dateien zu bearbeiten. Die Beispiele sind daher bewusst nicht spezielle physikalische Probleme sondern aus dem Alltag, die vorgestellten Techniken aber leicht auf das aktuelle Problem übertragbar. Beispiel1: Bilder sollen in Verzeichnisse entsprechend des Datums einsortiert werden Datum: exiftool gibt Informationen die im Bild hinterlegt sind aus, u.a. das Datum (Digitalkamera) exiftool $file exiftool -d "%F_%A" $file Best Practice: Datumformat: Jahr, Monat, Tag sorgt dafür das schon alles automatisch chronologisch richtig sortiert ist. Die meisten Applikationen sortieren lexikalisch von links nach rechts. exiftool -d "%F_%A" $file grep "Create Date" exiftool -d "%F_%A" $file grep "Create Date" cut -c 35- Wird länger -> besser als Skript speichern, kann dann auch wiederverwendet werden. #! /bin/sh for file in *.jpg; do date=$( exiftool -d "%F_%A" $file \ grep "Create Date" cut -c 35- ) echo $date done #! Interpreter Interpreter der das Programm ausführen soll.
Quell- und Zielverzeichnis in Variable, lässt sich dann leichter an einer Stelle ändern: #! /bin/sh SOURCEDIR=$PWD/Pictures TARGETDIR=$PWD/Ziel for file in ${SOURCEDIR}/*.jpg; do date=$( exiftool -d "%F_%A" $file \ grep "Create Date" cut -c 35- ) mkdir -p $TARGETDIR/$date cp $file $TARGETDIR/$date/ done
Dabei benutzt: 1. Umgebungsvariable $PWD ist das aktuelle Verzeichniss 2. \ am Ende der Zeile: Befehl wird in der nächsten Zeile fortgesetzt. Ermöglicht übersichtliche Formatierung auch bei langen Befehlen. 3. Pipe : Ausgabe des Befehls wird als Eingabe für den nächsten Befehl benutzt 4. mkdir -p fehlende übergeordnete Verzeichnisse, keine Fehlermeldung wenn das Verzeichniss schon existiert. Das geht aber schief wenn keine Info zum Datum existiert: if [ "$date" == "" ]; then date=unknown fi Bedingung if Testbefehl; then Befehle elif test; then Befehle else Befehle fi Testergebnis= 0 true sonst false [ ] eingebauter Testbefehl gibt 0 oder 1 zurück, je nach dem ob wahr oder falsch. Hier zum Stringvergleich benutzt.
Parameter Das Skript kann man noch öfter brauchen, aber man will doch nicht jedesmal die Variablen ändern: SOURCEDIR=$1 TARGETDIR=$2
Funktionen Brauchbares Skript, aber wenig später weiß man nicht mehr was es tut: usage() { cat << EOT Usage: $0 SOURCEDIR TARGETDIR Kopiert jpg-dateien aus SOURCEDIR in entsprechende Datums-Unterverzeichnisse von TARGETDIR EOT } if [ "$1" = "-h" ]; then usage exit 0 fi Funktionen [function] Name () { } Code Anzahl der übergebenen Argumente in $#, Argumente in $1...
Beispiel2: Dateien sollen umkopiert und dabei in umbenannt werden. Im Namen sollen die Dateien durchnummeriert werden. #! /bin/sh SOURCEDIR=$1 TARGETDIR=$2 SUFFIX=jpg TMPFILE=${SOURCEDIR}/file.lst echo "Filename?" read NAME if [ -e $TMPFILE]; then echo "Tempfile $TMPFILE exists..." echo "exiting..." exit 1 fi ls $SOURCEDIR/*.$SUFFIX > $TMPFILE COUNTER=0 while read FILE; do COUNTER=$(( COUNTER + 1 )) TARGET=$TARGETDIR/${NAME}-${COUNTER}.$SUFFIX cp $FILE $TARGET done < $TMPFILE
sed Stream-Editor Zeilen basiert Modifiziert (ohne explizite Option -i) nicht die Datei sondern gibt auf stdout aus Ausgabe muss dann bei Bedarf in eine neue Datei umgeleitet werden. Achtung idr. muss gequotet werden damit die Shell nichts interpretiert -> '... ' -n unterdrückt die automatische Ausgabe Bsp: sed '/regex/d' FILE sed -n '/regex/p' FILE sed -n '/BEGIN/,/END/p' FILE Suchen /regex/ Wichtigster Einsatz: Suchen und Ersetzen einmal pro Zeile s/regex/replacement/ mehrfach s/regex/replacement/g Stream: Hauptnutzung zum Editieren vo
awk (Autoren: Aho, Weinberger, und Kernighan) Problem: Dateien iv...dat Strom-Spannungskennlinien von Berechnungen der des Transports durch DNA verschiedener Länge (Basenpaare). Jede Datei ist das Ergebnis der Berechnung für eine Länge. Jetzt soll aber der Strom bei fester Spannung als Funktion der Länge, genauer log( I(V=-2.) ) vs. -log(länge) geplottet werden: Lösung: awk + xmgrace awk '/no_sites/ {x=$2} ($1==-2.0) {print -log(x) ''\t'' log( sqrt($2*$2) )}' * sort -n xmgrace -