[TUB] Servicebereich Weiterbildung + Zentraleinrichtung Rechenzentrum
Linux-Grundkurs - Shell-Programmierung
Druckfassung

Die Shell ist eine eigenständige Programmiersprache. Sie kennt Variablen und die meisten Kontrollstrukturen einer höheren Programmiersprache.

Inhalt


Das Shell-Script

Beispiel:
#!/bin/bash
# Kommentare werden durch dieses Zeichen kenntlich gemacht
echo Dateiverzeichnis von \ 
`pwd`: # Fortsetzungszeile
ls -la

Ein/Ausgabeumlenkung

In der Regel lesen Kommandos ihre Eingabe von der Standard-Eingabe (stdin, Kanal 0, normalerweise die Tastatur) und schreiben ihre Ausgabe auf die Standard-Ausgabe (stdout, Kanal 1, normalerweise der Bildschirm). Fehlermeldungen erfolgen auf die Fehlerausgabe (stderr, Kanal 2, ebenfalls der Bildschirm). Soll die Ein/Ausgabe von bzw. auf normale Dateien erfolgen, kann die Ein- bzw. Ausgabe umgelenkt werden.

Variablen (export, read)


Erweiterte Variablenersetzung

Neben der einfachen Einsetzung von Variablenwerten durch $NAME kennt die Bash Varianten, die die Ersetzung von Bedingungen abhängig machen. Hierbei geht es oft darum, was zu tun ist, wenn die Variable leer ist oder nicht existiert.
${variable:-value}
Default-Wert festlegen. Falls die Variable leer ist oder nicht existiert, setze value ein, ansonsten ihren Wert.
${variable:=value}
Default-Wert zuweisen. Falls die Variable leer ist oder nicht existiert, weise ihr value zu. Anschliessend setze ihren Wert ein.
${variable:+value}
Alternativ-Wert festlegen. Falls die Variable leer ist oder nicht existiert, setze einen leeren String ein, ansonsten den Alternativwert.
${variable:?value}
Fehler erzeugen. Falls die Variable leer ist oder nicht existiert, value als Fehlermeldung ausgeben, ansonsten den Inhalt der Variablen einsetzen.
${variable#value}
Falls der Variableninhalt mit value als Muster beginnt, setze den Variablenwert ein, wobei der Teil, der auf das Muster passt, gelöscht wird. Anderenfalls erfolgt keine Löschung beim Ersetzen.
${variable%value}
Falls der Variableninhalt mit value als Muster endet, setze den Variablenwert ein, wobei der Teil, der auf das Muster passt, gelöscht wird. Anderenfalls erfolgt keine Löschung beim Ersetzen.
Bei Weglassen des Doppelpunktes in den obigen Formen erfolgt nur Abfrage auf nicht existente Variablen, leere Inhalte werden wie andere behandelt.

Beispiele:

bash$ blubb=
bash$ echo ${blubb:-quark}
quark

bash$ blubb=käse
bash$ echo $blubb
käse

bash$ echo ${blubb:-quark}
käse
bash$ echo ${blubb:+quark}
quark

bash$ blubb=blah.f
bash$ echo ${blubb%.f}
blah
bash$ echo ${blubb%.x}
blah.f


Vordefinierte Variablen


Kommandosuchpfad (PATH)

Von der Shell auszuführende Befehle können eingebaute Kommandos (wie z.B. cd : change working directory) sein oder externe Kommandos, d.h. in Dateien abgelegte Programme oder Scripte (-> Unix Befehle). Die Shell bestimmt die Position der Dateien, die ein externes Kommando realisieren, anhand des absoluten oder relativen Pfadnamens
[Pfad/]name [ optionen ] [ parameter ]
Um die umständliche Angabe des Pfadnamens häufig benutzter Befehle zu vermeiden, führt die Shell eine Liste von Verzeichnissen, die die gebräuchlichsten Kommandos enthalten. Diese Liste ist in der Umgebungsvariablen
PATH
abgelegt. Wurde bei einem Befehl kein Pfad angegeben, durchsucht die Shell die Verzeichnisse in der Liste nach einer ausführbaren Datei mit dem Namen name. Verzeichnisse innerhalb der Liste werden durch das Zeichen : getrennt.

Beispiel:

export PATH=/usr/ucb:/bin:/usr/bin:/usr/local/bin:.
echo $PATH
Achtung: Das jeweils aktuell eingestellte Arbeitsverzeichnis ist nicht automatisch Bestandteil des Suchpfades. Es kann jedoch, wie im Beispiel, durch Angabe des Verzeichnisses "." in den Suchpfad aufgenommen werden. Zur Vermeidung unbeabsichtigter Überlagerung von Systemprogrammen durch gleichnamige Programme im aktuellen Verzeichnis sollte das aktuelle Verzeichnis, wenn überhaupt, nur als letztes durchsucht werden.

Das Kommando which vollzieht den gleichen Suchvorgang anhand der Variablen PATH wie bei der Befehlsausführung und liefert für einen Kommandonamen name den Namen des Verzeichnisses, indem die Datei zu finden ist.

Aufruf:

which name
which selbst ist für die Bash ein externes Programm (im Gegensatz z.B. zum tcsh-Kommandointerpreter) und muß deshalb selbst über den Suchpfad erreichbar sein oder mit absoluter Pfadangabe aufgerufen werden.

which befindet sich im Verzeichnis /usr/bin.

Beispiel:

which which
Ausgabe: /usr/bin/which

Das Kommando type teilt uns mit, ob ein Kommado eingebaut, ein alias, oder ein externes Programm ist.

Beispiel:

type which
Ausgabe: which is /usr/bin/which

Aufrufparameter (set, shift)


Fehlerbehandlung (set, &&, ||)


Kommandointerpretation (eval)

  1. Jede Scriptzeile wird in einzelne Kommandos zerlegt. Kommandos innerhalb einer Zeile bzw. Pipe werden durch die Metazeichen zur Ein/Ausgabeumlenkung sowie durch die Zeichen ; & ( ) | ^ getrennt. Einsetzen von Variablenwerten (z.B. ${NAME})
  2. Dateinamenexpansion: reguläre Ausdrücke (z.B. *.f) außerhalb von Klammern " ... " bzw. ' ... ' werden durch passende Dateinamen ersetzt.
    Beispiel:
    echo Dateien "*.c": *.c
  3. Innerhalb einzelner Kommandos werden die Trennzeichen $IFS interpretiert, um Kommandonamen von Parametern und Parameter untereinander zu trennen.
  4. Kommandosubstitution: Kommandos in Back-Quotes ` ... ` (von links oben nach rechts unten) werden ausgeführt und deren Standard-Ausgabe an die Stelle des Kommandos eingesetzt. Die Bash kennt daneben auch noch die Form $( ... ).
    Beispiel:
    echo `pwd`
  5. Durch das eval Kommando kann ein Kommando vor der Ausführung nochmals interpretiert werden.
    Beispiel:
    #!/bin/bash
    B=quoted; A='${B}'
    echo Inhalt von A = ${A} = `eval echo ${A}`
    
    Ausgabe: Inhalt von A = ${B} = quoted

Arithmetik (expr)

Das Kommando expr interpretiert Konstanten bzw. Werte von Variablen als ganze Zahlen, die miteinander verknüpft werden können.

Aufruf:

expr Loperant op Roperant
expr wertet den Ausdruck
Loperant op Roperant
aus und liefert das Ergebnis als Standard-Ausgabe. Der Operator op zur Verknüpfung des linken Operanden Loperand mit dem rechten Operanden Roperand steht für eine der folgenden Möglichkeiten:
+
Addition
-
Subtraktion
*
Multiplikation
/
ganzzahlige Division
%
Divisionsrest der ganzzahligen Division (Modulus)
Ausdrücke können mit runden Klammern ( .. ) zusammengefaßt werden. Einige der Symbole besitzen für die Shell eine besondere Bedeutung, z.B. das Zeichen * zur Dateinamenexpansion oder ( .. ) zur Zusammenfassung von Kommandos. Sie müssen daher durch einen Backslash \ oder durch Klammerung vor der Shell verborgen werden.

Da expr seinerseits ein Kommando ist, bietet die Bash auch ein eingebautes Analogon $(( ausdruck ))

Beispiel:

#!/bin/bash
...
NPLUS1=`expr $N + 1`
NPLUSplus=$(($N+1))

Hierbei ist allerdings zu beachten, dass Zahldarstellungen, die mit einer Null beginnen, als Oktalzahlen interpretiert werden und zu Fehlern führen, wenn sich eine 8 oder 9 darin verirrt hat.


Bedingungen (test, [])

Das Kommando test überprüft eine Bedingung und liefert den Exitstatus 0 (true), falls die Bedingung erfüllt ist. Es macht keine Ein- oder Ausgabe.

Aufruf:

test bedingung
oder
[ bedingung ]
In der zweiten Aufrufform ist [ ... ] kein Hinweis auf einen optionalen Parameter: [ ist ein Synonym für test Das Gegenstück ] schließt die Formulierung der Bedingung ab und startet die Auswertung.
bedingung
(Auswahl)
wahr, falls
Eigenschaften von Dateien
-f file file vorhanden und kein Verzeichnis ist
-d file file vorhanden und ein Verzeichnis ist
-s file file vorhanden und nicht leer ist
-x file file vorhanden und ausführbar ist
file1 -nt file2 file1 neuer als file2 ist (Modifikationsdatum)
file1 -ef file2 file1 identisch mit file2 ist (Links)
Vergleichen von Zeichenketten
str1 = str2 die Zeichenketten str1 und str2 gleich sind
str1 != str2 die Zeichenketten str1 und str2 nicht gleich sind
-z str die Zeichenkette str leer ist
Vergleichen von ganzen Zahlen
n1 -eq n2 die Zahlen n1 und n2 gleich sind
weitere mögliche Operatoren sind:
-ne
ungleich
-ge
größer oder gleich
-gt
größer
-le
kleiner oder gleich
-lt
kleiner

Verknüpfung von Bedingungen

!
Negierung (not)
-o
oder (or)
-a
und (and)
Beispiel:
test $S1 = $S2 -a $N1 -eq 0

Kontrollstruktur if

if kommandoliste
then kommandolisteT
else kommandolisteF
fi
Zunächst wird kommandoliste (z.B. ein test) ausgeführt. Falls der Exitstatus gleich 0 (d.h. true) ist, wird kommandolisteT ausgeführt, sonst kommandolisteF . Der else-Teil kann ggf. entfallen.

Beispiel:

befehl_11 und befehl_12 ausführen;
falls befehl_12 den Exitstatus 0 liefert, befehl_2 ausführen, sonst befehl_3.
if befehl_11; befehl_12
then befehl_2
else befehl_3
fi
Beispiel:
if [ -f file -a -w file ]; then cat >> file; fi

Kontrollstruktur for

for variable [ in wörterliste ]
do
kommandoliste
done
kommandoliste wird für jedes Wort der wörterliste einmal ausgeführt; die Variable variable enthält das jeweilige Wort der wörterliste. Entfällt wörterliste, wird $* angenommen.

Beispiel:

set *.f; for F; do f77 -c ${F}; done
Beispiel:
#!/bin/bash
for DIR in /tmp /usr/tmp ${HOME}/tmp
do
rm -rf ${DIR}/*
done
Beispiel:
#!/bin/bash
# Aufruf: mpost brief user1 user2 ...
# brief an Benutzer user1, user2, .. verschicken
BRIEF=$1; shift
# Jetzt sind nur noch user1, user2 ... positionale Parameter
for USER; do mail ${USER} < ${BRIEF}; done

Kontrollstruktur while

while kommandolisteB
do
   kommandoliste
done
Zunächst wird kommandolisteB ausgeführt. Liefert es den Exitstatus 0, wird kommandoliste abgearbeitet, dann wieder kommandolisteB ausgeführt, der Exitstatus überprüft usw.

Dies wird solange wiederholt, bis kommandolisteB erstmalig nicht den Exitstatus 0 liefert. Die Bearbeitung wird dann im Anschluß an done fortgesetzt.

Beispiel:

set *.f
while [ $# -gt 0 ]; do f77 -c $1; shift; done

continue und break

In einer Schleife (while, for, until) können die Befehle continue und break auftauchen.
continue bewirkt, dass die Bearbeitung der Schleife mit dem aktuellen Wert der Schleifenvariablen abgebrochen und mit dem nächsten Wert begonnen wird. Alle Kommandos in der Schleife, die dem continue fölgen, gelangen für den aktuellen Wert nicht mehr zur Ausführung.
break bewirkt das vollständige Verlassen der Schleife. Die Verarbeitung wird mit dem ersten Kommando hinter dem Ende der Schleife fortgesetzt.


Kontrollstruktur case

case wert in
   muster1) kommandoliste1 ;;
   muster2) kommandoliste2 ;;
   ...
   musterN) kommandolisteN ;;
esac
Die Shell wertet die Zeichenkette wert sowie die angegebenen Muster muster1 ... musterN aus. Beispiel:
#!/bin/bash
for w in * ; do
  case ${w} in
    *.f) f77 -c ${w};;
    \*) echo Das Verzeichnis ist leer;;
  esac
done
Die Namen aller Dateien des aktuellen Verzeichnisses werden nacheinander der Variablen w zugewiesen. Für jede konkrete Ersetzung wird überprüft, ob der Dateiname die Erweiterung .f besitzt (Muster *.f in der case Kontrollstruktur über den Wert ${w}). Falls ja, wird dieses Fortran-Programm mittels des F77-Compilers übersetzt. Alle anderen Dateien werden ignoriert.
Als ein Sonderfall wird die Situation behandelt, wenn das Verzeichnis leer ist: In diesem Falle findet bei * keine Dateinamensubstitution statt, sondern das Zeichen * bleibt erhalten. Diese Situation soll mit einem Muster innerhalb des case-Konstruktes abgefangen werden. Da das Muster * aber auf alle Zeichenketten paßt, muß diese Sonderbedeutung mit dem Zeichen \ aufgehoben werden. Das Muster \* paßt nur auf das Zeichen *.

Shellfunktionen

In der Bash lassen sich auch Unterprogramme formulieren, die man wie eingebaute Funktionen benutzen kann.
function() {
   kommandoliste
}

Genau wie ein Shellscript findet die Funktion ihre Argumente in den positionalen Parametern $1, $2, .. wieder. Da eine Funktion formal wie ein Programm/Script behandelt wird, hat sie auch einen Rückgabewert. Mit dem Befehl return value wird die Funktion beendet und der Rückgabewert erzeugt.

Beispiel:

action() {
  STRING=$1
  echo -n "$STRING "
  shift
  initlog $INITLOG_ARGS -c "$*" && success "$STRING" || failure "$STRING"
  rc=$?
  echo
  return $rc
}

Beispiel:

confirm() {
  echo -n "Start service $1 (Y)es/(N)o/(C)ontinue? [Y] "
  read answer
  case $answer in
    y|Y|"")
      return 0
    ;;
    c|C)
      return 2
    ;;
    n|N)
      return 1
    ;;
    *)
      confirm $1
      return $?
    ;;
    esac
}