Shell-Programmierung

Die Bourne Again Shell ist ein POSIX-konformer Kommando-Interpreter für Programme wie dieses:

:(){ :|: & };:

Beispiel

Ein Shellskript besteht aus einer Folge von Kommandos. Die erste Zeile kann (und sollte) einen speziellen Kommentar (das shebang) enthalten, welches den zu verwendenen Interpreter enthält.

hello.sh
#!/usr/bin/env bash if [ $# -eq 0 ]; then echo Hello, World\! else for arg; do echo Hello, $arg\! done fi

Ausführbar machen und ausführen mit:

chmod +x hello.sh && ./hello.sh
Hello, World!

Argumente übergeben

Ein Kommando besteht aus dem Namen einer Funktion oder eines Programms gefolgt von einer Liste mit Argumenten. In Shellskripten wird die Argumentliste in den Positionsparametern gespeichert. In C-Programmen greift man auf die Argumentliste über den Parameter argv der Funktion main zu.

Programm Option Option Parameter Operand
Kommandozeile mkdir -p -m 755 ~/foo/bar
Positionsparameter $0 $1 $2 $3 $4
Argumentliste in C argv[0] argv[1] argv[2] argv[3] argv[4]

Argumente, die mit einem - beginnen, nennt man Optionen. Manche Optionen haben wiederum Parameter (optarg). Sie steuern üblicherweise das Verhalten des Programms. Das Programm getopts extrahiert alle Optionen aus der Argumentliste. Übrig bleiben die Operanden, mit denen das Programm arbeitet.

Umgebung auslesen

Die Shell verwaltet (wie jeder Prozess) eine Liste von Name-Wert-Paaren, die sogenannte Umgebung. Exportierte Variablen vererbt sie an Kindprozesse. Folgende Variablen stehen immer zur Verfügung:

0…9
Positionsparamter für Argumente
*
Alle Positionsparamter als Token, wie "$1 $2 …"
@
Alle Positionsparamter als Liste, wie "$1" "$2" …
#
Anzahl der Positionsparamter
?
Exit-Status des letzten Kommandos
-
Flags beim Aufruf der Shell
$
Eigene Prozessnummer
!
Prozessnummer des zuletzt gestarteten Kindprozesses
_
Das letzte übergebene Argument
RANDOM
Pseudo-Zufallszahl (nur Bash)

Variablen substituieren

Variablen haben einen Namen und einen Wert. Arrays verhalten sich wie Hashmaps mit numerischen Schlüsseln. Die Bash erlaubt seit Version 4 auch Zeichenketten als Schlüssel.

null=
name="/foo/bar.txt"
array=(foo bar fnord)
declare -A hash=([foo]=fnord [bar]=snafu)

Das Symbol $ steht für Substitution. Findet die Shell diesen Operator, expandiert sie die entsprechende Variable nach den unten beschriebenen Regeln, bevor sie das Kommando ausführt.

Variable Beispiel Ausgabe Expansion
$name $name /foo/bar.txt Wert der Variable
${name} ${name}_ /foo/bar.txt_ Dito
${array[key]} ${array[0]} foo Wert des Elements
${array[*]} ${array[*]} foo bar fnord Werte als Token
${array[@]} ${array[@]} foo
bar
fnord
Werte als Liste
Defaults für Null-Variable
${name:+alternate} ${name:+hello} Alternative oder gar nichts.
${name:-default} ${name:-hello} /foo/bar.txt Defaultwert
${name:=default} ${null:=hello} hello Defaultwert mit Zuweisung an Variable
${name:?message} ${null:?hello} hello Meldung auf stderr; beendet die Shell
Untermengen
${name:offset} ${name:4} bar.txt Ab dem n-ten Zeichen
${name:offset:length} ${name:4:3} bar Ab dem n-ten Zeichen mit Länge
Variablennamen
${!prefix*} ${!n*} name null Variablennamen mit Präfix als Token
${!prefix@} ${!n@} name
null
Variablennamen mit Präfix als Liste
${!array[*]} ${!array[*]} 1 2 3 Alle Schlüssel als Token
${!array[@]} ${!array[@]} 1
2
3
Alle Schlüssel als Liste
Länge
${#name} ${#name} 12 Länge des Werts
${#array[key]} ${#array[2]} 5 Länge des Elements
${#array[@]} ${#array[@]} 3 Anzahl der Elemente
Ersetzung
${name#prefix} ${name#*/} foo/bar.txt Präfix abschneiden
${name##prefix} ${name##*/} bar.txt Präfix gierig abschneiden (basename)
${name%suffix} ${name%/*} /foo Suffix abschneiden (dirname)
${name%%suffix} ${name%%/*} Suffix gierig abschneiden
${name/pat/rep} ${name/o/l} /flo/bar.txt Einmal ersetzen
${name//pat/rep} ${name//o/u} /fuu/bar.txt Alle ersetzen
${name/#pat/rep} ${name/#\/foo/fnord} fnord/bar.txt Vorne ersetzen
${name/%pat/rep} ${name/%txt/jpg} /foo/bar.jpg Hinten ersetzen
Kommando-Substitution
`command` `uname` Linux Ausgabe des Kommandos, veraltet
$(command) $(uname) Linux Ausgabe des Kommandos
<(command) <(uname) Ausgabe von Kommando Umleiten
>(command) >(uname) Ausgabe zu Kommando Umleiten
Arithmetik
$((expression)) $((x + 3)) 8 Arithmetische Erweiterung
Wert-Expansion
{a..z} foo{a..c} fooa foob fooc Kombinationen mit einer Spanne von Zeichen
{str,…} foo{bar,baz} foobar foobaz Kombinationen mit einer Liste von Zeichenketten
~ ~ /home/john Benutzerverzeichnis des Prozessbesitzers
~username ~alice /home/alice Benutzerverzeichnis eines anderen Benutzers
* foo* foo foo.txt Beliebige Zeichenkette
? ?oo foo xoo zoo Beliebiges Zeichen
[liste] [a-m,x,y]oo foo xoo Ein Zeichen aus einer Gruppe

Ablauf steuern

command1 && command2 || command3
Ternärer Operator
if [[ expression ]]; then …; [ elif [[ expression ]]; then …; else …; ] fi
Verzweigung
case name in glob) …;; esac
Mehrfachverzweigung
while [[ expression ]]; do …; done
Schleife mit Forsetzungsbedingung
until [[ expression ]]; do …; done
Schleife mit Abbruchbedingung
for name; do …; done
Iteration über Positionsparameter
for name in list; do …; done
Iteration über Liste
for ((expr1; expr2; expr3)); do …; done
Iteration wie in C
break
Verlässt die for-, while- oder until-Schleife
continue
Setzt die Schleife beim nächsten Durchlauf fort
name() { … }
Definiert eine Funktion
return code
Verlässt die Funktion
trap action signal
Registriert Funktion für Signalbehandlung

Logik auswerten

[[ expression ]]
Wahr, wenn
Lexikographisch
-z s2 Zeichenkette ist leer
-n s2 Zeichenkette enthält mindestens ein Zeichen
s1 = s2 Zeichenketten sind identisch
s1 == s2 Dito
s1 != s2 Zeichenketten unterscheiden sich
s1 < s2 Linke lexikographisch kleiner als rechte Zeichenkette
s1 > s2 Linke lexikographisch größer als rechte Zeichenkette
Arithmetisch
! expressionNegation, Ausdruck ist falsch
x -eq y Gleichheit
x -ne y Ungleichheit
x -lt y Linke Zahl kleiner als rechte Zahl
x -le y Linke Zahl kleiner oder gleich rechte Zahl
x -gt y Linke Zahl größer als rechte Zahl
x -ge y Linke Zahl größer oder gleich rechte Zahl

Arithmetisch rechnen

(( expression ))
Wahr, wenn
(…) Präzedenz
x = y Zuweisung
x += y Zuweisung mit Addition
++ y Increment
-- y Decrement
~ y Bitkomplement
x & y Bitweise UND verknüpfen
x ^ y Bitweise ODER verknüpfen
x ** y Exponent
x * y Multiplikation
x / y Division
x % y Modulo
x + y Addition
x - y Subtraktion

Datenströme umleiten

Die Shell ist über Standardeingabe und Standardausgabe mit einem (Pseudo-)Terminal verbunden. Pipes verbinden die Standardausgabe eines Prozesses mit der Standardeingabe des nächsten Prozesses.

command1 | command2 > file

In diesem Beispiel wird die Eingabe von der Tastatur durch 2 Prozesse gefiltert und die Ausgabe in einer Datei abgelegt. Nur die Fehlerausgabe wird über das verbundene Terminal am Bildschirm angezeigt.

(Standard)eingabe
command n< file Aus Datei lesen
command n<< delim Bis zum Trenner lesen (Here-Dokument)
command n<<- delim Dito, aber Einrückung ignorieren
command n<&m Deskriptor duplizieren
command n<&- Deskriptor schließen
(Standard)ausgabe
command n> file In Datei schreiben
command n>| file Datei überschreiben, selbst wenn noclobber gesetzt ist
command n>> file An Datei anhängen
command n>&m Deskriptor duplizieren
command n>&- Deskriptor schließen
command n<> file Deskriptor zum Lesen und Schreiben öffnen

Legende

n, m
0 = Standardeingabe
1 = Standardausgabe
2 = Standardfehlerausgabe

Beispiele

Standardfehlerausgabe im Pager betrachten

command 2>&1 | less

Der Operator > leitet die Ausgabe von Kommandos und Funktionen in Dateien um.

for i in {1..10}; do echo "Hello $i"; done > file

Umgekehrt leitet < den Inhalt von Dateien in die Standardeingabe um. Um zum Beispiel eine Datei zeilenweise zu lesen:

while read line; do echo $((++n)) $line; done < file

Und so leitet man die Standardausgabe eines Kommandos in die Standardeingabe um:

while read line; do echo $line; done < <(command)

Umleitung eines Here-Dokuments in ein Kommando:

cat <<- .
the quick brown fox jumps
over the lazy dog
.

Reguläre Ausdrücke

basic extended perl joe
Zeilenanfang ^ ^ ^ \^
Zeilenende $ $ $ \$
Wortanfang \<
Wortende \>
Ein beliebiges Zeichen . . . \?
Ein Element der Zeichenklasse [] [] [] \[]
Kein Element der Zeichenklasse [^] [^] [^] \[^]
Buchstabe [a-zA-Z] [:alpha:] \[a-zA-Z]
Kleinbuchstabe [a-z] [:lower:] \[a-z]
Großbuchstabe [A-Z] [:upper:] \[A-Z]
Ziffer [0-9] [:digit:] \d \[0-9]
Hexadezimalziffer [0-9a-fA-F] [:xdigit:] \[0-9A-Fa-f]
Alphanumerisches Zeichen [0-9a-zA-Z] [:alnum:] \[0-9a-zA-Z]
Wort-Zeichen [:word:] \w
Leerzeichen [:blank:] \[ \t]
Steuerzeichen [:cntrl:] \[\x00-\x1F\x7F]
Unsichtbares Zeichen [:space:] \s \[ \t\r\n\v\f]
Sichtbares Zeichen [:graph:]
Druckbares Zeichen [:print:]
Interpunktionszeichen [:punct:]
Gruppierung \(\) () ()
Rückreferenz auf n-te Gruppe \n \n
Alternative | |
Ein oder Kein Vorkommen ? ?
Beliebig viele Wiederholungen * * * \*
Eine oder mehr Wiederholungen + + \+\[]
Genau n Wiederholungen \{n\} {n} {n}
n bis m Wiederholungen \{n,m\} {n,m} {n,m}

Literatur

  1. Robbins, Beebe: Klassische Shell-Programmierung, O'Reilly
  2. Joshua Levy: The Art of Command Line
  3. Mendel Cooper: Advanced Bash-Scripting Guide, TLDP
  4. Steve Litt: Perl Regular Expressions