Zu den Prozessattributen gehören :
Beim Beenden gibt der Prozess einen Exit Code an die Umgebung
zurück. Per Konvention zeigt eine 0
an, daß der
Prozess erfolgreich war, andere Werte (1-255
) einen
Mißerfolg.
0
= Standard Input
1
= Standard Output
2
= Standard Error
Die prinzipielle Arbeitsweise ist die, wiederholt interaktiv Befehle entgegenzunehmen, diese zu interpretieren und auszuführen. Diese Befehle können jedoch auch von einer Datei gelesen werden. Eine solche Datei nennt man dann ein Shellscript. Shellscripts werden nicht-interaktiv ausgeführt.
Neben der ursprünglichen Bourne Shell entstanden im Laufe der Zeit andere Programme, die Aufgaben der Shell übernehmen können :
#
am Anfang
eines Wortes und der nächten Zeilenschaltung ein Kommentar.
Variablennamen werden aus Buchstaben (A-Z, a-z), Ziffern (0-9) und dem Underscore (_) gebildet.
Die Zuweisung einer Variablen erfolgt duch den Befehl
var=value
Den Wert (Inhalt) einer Variablen erhält man durch einen der Ausdrücke :
$var ${var}
Die zweite Form ist interessant, wenn unmittelbar hinter dem Variablennamen Zeichen folgen sollen, die sonst als Bestandteil des Variablennamens betrachtet würden.
Variablen können mit gleichnamigen Environment-Variablen verbunden werden. Dadurch werden Änderungen der Shell-Variablen automatisch in die entsprechenden Environment-Variablen übertragen.
Die Verbindung erfolgt durch den export
-Befehl :
export var
Beim Aufruf der Shell stehen alle übernommenen Environment-Variablen als Shell-Variablen zur Verfügung. Diese sind jedoch nicht exportiert.
Es gibt auch Variablennamen, die aus einem Sonderzeichen bestehen. Diese haben eine besondere Bedeutung und werden in den folgenden Kapiteln erklärt.
PATH
in verschiedenen Directories gesucht.
pwd
Das Programm wird im "Vordergrund" aufgerufen.
Die Shell wartet daraufhin bis zur Beendigung des Prozesses.
Anschließend steht der Exit Code des Prozesses in der speziellen
Variablen ?
zur Verfügung.
Es können mehrere Befehle durch ;
getrennt
in einer Zeile angegeben werden :
date; pwd; ls
Dann werden die einzelnen Befehle hintereinander ausgeführt.
Soll die Shell nicht warten, wird das Programm durch Angabe des Zeichens
&
im "Hintergrund" aufgerufen :
xterm &
Natürlich steht der Exit Code hiernach nicht zur Verfügung,
weil der Prozess ja i. a. noch läuft.
Stattdessen steht in der speziellen Variablen !
die
Prozessnummer zur Verfügung.
Auch die Shell selber hat natürlich eine Prozessnummer. Sie
läßt sich über die spezielle Variable $
ermitteln.
Parameter werden durch Spaces getrennt hinter den Programmnamen gesetzt :
ls -l mydir xterm -bg blue &
Die aufgerufenen Prozesse bekommen alle Prozessattribute der Shell übergeben, insbesondere die Environment-Variablen und offene Dateien.
Die Parameter, die einem Shellscript übergeben wurden, können darin
über spezielle Variablen ermittelt werden. Die Variable #
gibt die Anzahl der Parameter an. Die Parameter selbst stehen in den
Variablen 1
, 2
, usw. und der Name des Shellscriptes
in der Variablen 0
.
Auch Shellscripts können wie Programme über ihren Namen aufgerufen
werden, indem sie mit Ausführungsberechtigung versehen werden und in den
Suchpfad (PATH
) gestellt werden.
Es gibt eine Reihe in der Shell eingebaute Befehle, die nicht als Programme aufgerufen werden, sondern von der Shell selber interpretiert werden.
Eines dieser Befehle ist echo
, das alle Parameter einfach
über Standard Output ausgibt.
Dies kann für kurze Nachrichten oder Tests verwendet werden.
Ein anderer eingebauter Befehl ist wait
, der auf die Beendigung
eines vorher im Hintergrund gestarteten Programmes wartet und dessen Exit Code
verfügbar macht :
wait $! echo $?
Ein weiterer Befehl ist exec
. Dieser bewirkt, daß der
Shellprozeß durch den als Parameter angegebenen Prozeß
ersetzt wird :
exec echo All work done.
Die Beendigung eines Shellscripts erfolgt implizit durch das Erreichen des
Endes oder explizit durch den Befehl exit
, ggf. unter Angabe
des Exit-Codes :
exit 1
fileno<filename fileno>filename fileno>>filename <filename >filename >>filename
Sie leiten die angegebene offene Datei als Eingabe (<
) bzw.
als Ausgabe (>
) auf die Datei des angegebenen Namens um.
Sinnvollerweise sind die Default-Filenummern (fileno) die des
Standard Input bzw. des Standard Output. >>
bewirkt ein Anhängen der Ausgabe an die angegebene Datei.
ls -l 1>list.txt 2>error.txt sort +2 <data.txt >result.txt date >>logfile
Es kann auch im Shellscript eine Eingabe angegeben werden :
cat <<EOF >testfile These are lines that all go into the output file. The shell received $# parameters. Our home is at $HOME and not where the heart is. EOF
Die Angabe hinter <<
dient hierbei als Kennzeichen
für das Ende der Eingabe.
Statt eines Dateinamens kann auch eine (andere) Dateinummer angegeben werden :
fileno1<&fileno2 fileno1>&fileno2 <&fileno2 >&fileno2
Dies bewirkt die Umleitung einer offenen Datei fileno1 auf eine andere offene Datei fileno2. Auch hier kann vereinfachend Standard Input oder Standard Output verwendet werden. Hierdurch kann man offene Dateien "kopieren".
Wird statt fileno2 -
angegeben, so wird die Datei
stattdessen geschlossen.
In folgendem Beispiel werden Standard Output und Standard Error auf die selbe Datei umgeleitet :
find /tmp -user $USER -print 1>list.txt 2>&1
Über den Befehl exec
kann eine permanente Umleitung
erfolgen :
exec 2>error.log rmdir /tmp
Eine Datei kann dadurch auch für eine Passage umgeleitet werden :
exec 3>&1 1>status.txt date pwd ls -l exec 1>&3 3>&-
Pipelines werden erzeugt, indem mehrere Befehle hintereinander durch
|
angegeben werden :
ls -l | sort +4n
Für jedes |
wird eine Pipeline erzeugt und jeweils der
Standard Output des linken Befehls und der Standard Input des rechten
Befehls auf diese Pipeline umgeleitet.
Auch Kombinationen sind möglich :
find /tmp -name test -print 2>&1 | xargs ls -l | tee logfile ls -al | fgrep testuser | sort +4n >testfiles cpio -it <data.cpio | less
Der jeweils ganz rechts stehende Befehl wird dabei im Vordergrund, alle anderen im Hintergrund ausgeführt. Anschließend steht der Exit Code des rechten Befehls zur Verfügung.
{ date pwd ls -l } >status.txt
Die Shell kann auch geteilt werden, um ein Teil-Shellscript auszuführen :
( cd subdir ls -t ) >subfiles.txt
Da hier ein neuer Shellprozess erzeugt wird, kann dieser seine Prozessattribute ändern, ohne daß sich dies auf die "umliegende" Shell auswirkt.
Eine einfache Unterscheidung erfolgt mit if
:
if expr then command1 else command2 fi
Zunächst wird expr als Befehl ausgeführt.
Ist sein Exit-Code 0
, so wird command1 ausgeführt,
sonst command2.
Für command können auch jeweils mehrere Befehle
angegeben werden. Der else
-Teil kann auch entfallen.
Eine verkürzte Syntax ermöglichen &&
und
||
:
command1 && command2 command3 || command4
command2 wird nur dann ausgeführt, wenn command1
einen Exit-Code 0
liefert und command4 nur dann,
wenn command3 einen Exit-Code ungleich 0
liefert.
Eine Unterscheidung nach Textmustern geschieht durch case
:
case string in pattern1) command1 ;; pattern2) command2 ;; pattern3) command3 ;; esac
string ist ein beliebiger Text, der meistens aus einer Variablen stammt. Er wird der Reihe nach auf alle Muster pattern geprüft. Paßt eines, so wird der entsprechende Befehl ausgeführt und das Konstrukt beendet. Es wird also höchstens einer der Teile ausgeführt. Es können beliebig viele Teile angegeben werden. Auch hier können für command jeweils mehrere Befehle angegeben werden.
pattern ist ein beliebiger Text. Bestimmte Zeichen und Zeichenfolgen haben darin jedoch eine besondere Bedeutung :
pattern1|pattern2
verknüpft zwei Muster durch "oder".
*
steht für eine beliebige Zeichenfolge.
?
steht für ein beliebiges Zeichen.
[charlist]
steht für eines der Zeichen
in charlist
[char1-char2]
steht für eines
der Zeichen im Code-Bereich von char1 bis char2
Das Muster *
paßt auf alle Texte und kann somit sinnvoll
als letztes Muster verwendet werden.
Zur Bildung von Schleifen gibt es while
:
while expr do command done
Zunächst wird expr als Befehl ausgeführt.
Ist sein Exit-Code 0
, so wird command ausgeführt
und der Vorgang wiederholt, sonst wird das Konstrukt beendet.
Für command können auch hier mehrere Befehle
angegeben werden.
Eine aufzählende Schleife wird mit for
gebildet :
for var in wordlist do command done
wordlist ist hier eine Liste von Texten (Wörter), durch Spaces getrennt. Für jedes dieser Wörter wird der Variablen var das jeweilige Wort zugewiesen und dann der Befehl command ausgeführt.
Wird in wordlist
ausgelassen, so werden alle dem
Shellscript übergebenen Parameter verwendet.
Enthält ein angegebenes Wort eines dieser Zeichen, so wird es als Muster betrachtet und durch eine Liste aller Dateinamen ersetzt, die diesem Muster entsprechen.
Die besonderen Zeichen und Zeichenfolgen sind :
*
steht für eine beliebige Zeichenfolge, die kein
/
enthält.
?
steht für ein beliebiges Zeichen außer
/
.
[charlist]
steht für eines der Zeichen
in charlist
[char1-char2]
steht für eines
der Zeichen im Code-Bereich von char1 bis char2
Die Zeichen *
und ?
umfassen nur dann einen Punkt,
wenn sie weder am Anfang eines Musters noch hinter einem /
stehen.
Alle diese Angaben lassen sich innerhalb eines Musters mehrfach und an
beliebigen Stellen verwenden. Das Zeichen /
bedeutet wie in allen
Dateinamen das Trennzeichen für Directories und läßt sich
entsprechend in Mustern verwenden :
*/test
test
in allen
Subdirectories erster Ebene
test*/*.c
.c
enden, in allen Subdirectories erster Ebene, deren Name mit
test
beginnt.
*[0-9A-Z_]*
_
enthalten.
.??*
Wird zu dem angegebenen Muster kein passender Dateiname gefunden, so bleibt das Muster als Wort unverändert.
Unabhängig von Dateinamen lassen sich auch über einen
Vervielfältigungsmechanismus Wortlisten erzeugen. Dazu dienen
geschweifte Klammern {}
und das Komma.
Enthält ein Wort die Zeichenfolge {wordlist}
,
wobei wordlist eine durch Komma getrennte Liste von Wörtern
ist, so wird dies als ein Listenmuster betrachtet. Dies bewirkt, daß
es durch eine Liste von ähnlichen Wörtern ersetzt wird, die
entstehen, indem jeweils der umliegende Text des Musters mit einem der
Wörter der wordlist kombiniert wird :
abc{1,22,333}xyz -> abc1xyz abc22xyz abc333xyz abc{A,B}{1,2}xyz -> abcA1xyz abcA2xyz abcB1xyz abcB2xyz
Diese Listenerzeugung erfolgt vor der Filename Generation und läßt sich daher damit kombinieren :
test*.{c,h,txt} -> test*.c test*.h test*.txt
Die Listenerzeugung ist eine Fähigkeit der bash, jedoch nicht der Bourne Shell.
Das Escape-Zeichen \
nimmt dem nachfolgenden Zeichen
seine besondere Wirkung. Am Zeilenende bewirkt es, daß die nachfolgende
Zeile als Fortsetzung der Zeile betrachtet wird. Dadurch lassen sich lange
Zeilen aufspalten, ohne ihre Wirkung zu ändern.
echo Alles ok \? echo \<HTML\> echo \*\*\* Error ! echo Dies \\ ist ein Backslash. echo \# Dies ist kein Kommentar. echo Diese Zeile wird in zwei Zeilen aufgeteilt, \ weil sie sonst zu lang ist.
Eine andere Möglichkeit für die direkte Angabe von Schriftzeichen
ist das Quoting. Der fragliche Text wird dazu innerhalb von
Anführungszeichen gesetzt. Es stehen dafür die einfachen
('
) und die doppelten ("
) Anführungszeichen
zur Verfügung.
Innerhalb von einfachen Anführungszeichen ('
) verlieren alle
Zeichen ihre besondere Wirkung, sogar die Zeilenschaltung. Daher
läßt sich das Anführungszeichen selber nicht damit darstellen.
Innerhalb von doppelten Anführungszeichen ("
) behalten
nur folgende Zeichen ihre besondere Wirkung :
"
beendet das Quoting, kann daher nicht direkt
verwendet werden.
$
mit nachfolgendem Variablennamen wird wie gewohnt durch
den Inhalt der Variablen ersetzt.
\
verhindert die besondere Wirkung des nachfolgenden
Zeichens, sofern dies \
selber, "
,
$
oder eine Zeilenschatung ist.
Sonst bleibt \
erhalten.
Jeder Text innerhalb von Anführungszeichen wird als (höchstens) ein Wort aufgefaßt. Quoting läßt sich jedoch auch auf einen Teil eines Wortes anwenden.
echo 'Quotes sind '"'"' und "'
Hier wird 3 mal Quoting betrieben :
Quotes_sind_
'
_und_"
Das Ergebnis ist ein Wort, da alle Quotings zusammenhängen.
Es gibt zwei spezielle Variablen, die eine Liste der Parameter enthalten. Deren Wirkung unterscheidet sich, je nach dem, ob sie in doppelten Anführungszeichen stehen, oder nicht :
$*
und $@
"$*"
"$@"
Hierzu hilft auch der Befehl shift
. Er löscht den ersten
Parameter und schiebt alle folgenden einen Platz zurück. Dies kann
verwendet werden, um die ersten Parameter gesondert zu behandeln und alle
folgenden als Liste.
Eine andere Form des Quotings bietet das Back-Quote (`
).
Darin wird ein Befehl eingeschlossen. Hierin haben folgende Zeichenfolgen
eine besondere Bedeutung :
\\
liefert \
.
\$
liefert $
.
\`
liefert `
. Dies läßt sich
zur geschachtelten Ausführung verwenden.
Der Befehl wird von einer neuen Shell interpretiert (einschließlich Quoting) und ausgeführt und dessen Standard Output wird als Text in die Befehlszeile eingesetzt.
ls -ld `find /tmp -user $USER -print` echo "The time now is: `date`"
Ohne doppelte Anführungszeichen wird die Befehlsausgabe als Wortliste eingesetzt und Filename Generation durchgeführt. Mit Anführungszeichen wird sie als ein Wort eingesetzt.
Ein geschachteltes Beispiel :
TEXT='Example. "Text"' t="`echo \"\$TEXT\" | sed \"s/\\\\./:/g;s/\\\"/'/g\ "`" # `echo "$TEXT" | sed "s/\\./:/g;s/\"/'/g"` # echo "$TEXT" | sed "s/\./:/g;s/\"/'/g" # sed s/\./:/g;s/"/'/g echo "$t" # Example: 'Text'
Escaping und Quoting läßt sich auch auf die Muster des
case
-Befehls und für die Dateiumleitung anwenden.
Ähnlich der Back-Quotes führt der in der Shell eingebaute Befehl
eval
eine Re-interpretation seiner Parameterliste durch und
führt den entstehenden Befehl aus.
Dies beinhaltet Variablen-Ersetzung, Filename Generation, Escaping und Quoting.
Dies kann zur dynamischen Konstruktion beliebiger Befehle verwendet werden.
echo 'echo "$HOME"' >evaltest eval `cat evaltest`
Funktionen wirken ähnlich wie Befehle. Ihnen können Parameter übergeben werden und sie haben einen Rückgabewert, einem Exit-Code vergleichbar.
liste() { echo "Liste #$1: $2" ls -l $3 return 0 } liste 1 "Text-Dateien" "*.txt" liste 2 "Bild-Dateien" "*.png *.jpg" liste 3 "Dokumentation" "*.doc"
Der return
-Befehl gibt den Rückgabewert an.
Der Befehl exit
kann hierfür nicht verwendet werden,
weil dieser auch innerhalb einer Funktion die Shell beendet.
Zur Ausführung einer Funktion wird keine neue Shell erzeugt, daher wirken sich Änderungen der Prozessattribute auch auf die Aufrufumgebung aus. Lediglich die Parameter sind getrennt.
Innerhalb einer Funktion können lokale Variablen deklariert werden :
local var
Diese Variablen sind nur innerhalb der betreffenden Funktion und aller darin aufgerufenen Funktionen sichtbar.
Ein rekursiver Aufruf von Funktionen ist möglich.
Justage der Berechtigungen eines Directory-Baumes :
find . -type d -print | xargs chmod u+rw,go-rw,a+x find . -type f -print | xargs chmod u+rw,go-rw
Umwandeln einer Liste von Dateien :
ls *.png | sed 's/\.png$//' | while read file do p="$file.png" j="$file.jpg" if [ ! -f "$j" ] then echo "$p -> $j" cjpeg "$p" >"$j" fi done
Erstellung einer Backup-Archivdatei :
BACKUPDIR=/usr/local/data SUBDIRS="mydata otherdata" BACKUPARCH=data.cpio cd $HOME/backup [ -f $BACKUPARCH ] && mv $BACKUPARCH $BACKUPARCH.old ( cd $BACKUPDIR find $SUBDIRS ! -name '*.tmp' -print | cpio -o ) >$BACKUPARCH ls -l $BACKUPARCH
Beenden eines Service-Daemons :
PIDFILE=service.pid if [ -s $PIDFILE ] then p=`cat $PIDFILE` kill "$p" n=0 while [ $n -lt 10 ] do if kill -0 "$p" then sleep 1 n=`expr $n + 1` else >$PIDFILE exit 0 fi done kill -9 "$p" >$PIDFILE exit 1 else exit 0 fi
Bereinigung der Namen eines Directory-Baumes :
CLEAN=' { d = "" a = $0 if (a ~ "/") { d = a sub("[^/]*$","",d) sub("^.*/","",a) } b = a gsub(" ","_",b) gsub("&","+",b) if (a != b) { print d a print d b } }' find "$@" -depth -print | awk "$CLEAN" | while read a do read b mv "$a" "$b" done
Rekursive Directoryliste ohne den find
-Befehl
subdir() { local d f d="$1" echo "$d" ls "$d" | while read f do [ -d "$d/$f" ] && subdir "$d/$f" done } subdir .
\
und Anführungszeichen läßt
sich steuern, welcher Text als Wort oder als Wortliste interpretiert
wird und welche besonderen Zeichen interpretiert werden.