Aufzucht und Hege von Shell-Scripts: Sub-Shells
Wann und wie entstehen Sub-Shells und "Children"?
Am laufenden Band ;-) Ist das schlimm? Nein, ist es nicht, das ist ja der Sinn eines Betriebssystems. Es ist nur dann
schlimm im Sinne von Ressourcenvergeudung und schlechterer Performance, wenn überflüssige Prozesse eigentlich vermieden
werden könnten. Einige Tipps dazu findet Ihr im Abschnitt Script-Overclocking.
Hier geht es darum, wann in Eurem Script neue Prozesse erzeugt werden, wann dies Sub-Shells sind (eine Auswirkung
davon ist im Abschnitt Variablen beschrieben) und wie Ihr das bei Bedarf verhindern
könnt.
Kindprozesse ("Children") Eurer Shell entstehen z. B.:
Wenn Ihr ein externes Programm aus Eurem Script heraus startet
Ein Shell-Builtin verursacht keinen neuen Prozess. Wie Ihr herauskriegen könnt, ob ein benutzter Befehl ein
Shell-Builtin ist oder nicht, erfahrt Ihr im Abschnitt Finden. Auch andere Shell-Scripts
sind externe Programme (es sei denn, Ihr verfahrt wie weiter unten beschrieben).
Wenn Ihr eine Pipe benutzt.
Alles hinter der Pipe läuft in einer Sub-Shell ab - mit den oben beschriebenen Auswirkungen z. B. auf die Gültigkeit
von Variablen, aber auch auf andere Parameter Eurer Umgebung. Versucht mal folgendes:
jan@jack:~/tmp/subshell> cat chdir.sh #! /bin/bash cd subdir pwd jan@jack:~/tmp/subshell> ./chdir.sh /home/jan/tmp/subshell/subdir jan@jack:~/tmp/subshell> pwd /home/jan/tmp/subshell
So geht das also nicht. Wie dann? Je nachdem, was Ihr wollt. Hier ein Beispiel dafür, wie Ihr die in einer Subshell ermittelten Variablenwerte nach oben durchreichen könnt. Das Prinzip ist einfach: Schreibt das Ergebnis Eurer Befehle in der Subshell auf stdout und fangt es in der aufrufenden Shell per Kommandosubstitution ab (beachtet wieder das richtige Quoting, da Ihr mit einer Liste mehrerer Dateien rechnen müsst):
# alle Dateien in einem Verzeichnisbaum suchen, die den Text "Hallo" enthalten
HALLO_FILES="`find . -type f -print | xargs grep -l 'Hallo'`"
# das 1. Verzeichnis suchen, das mit "hallo" beginnt und eine Datei namens "Hallo"
# enthaelt; dann hinein wechseln
NEW_DIR="`find . -type d -name 'hallo*' -print | while read dir; do
if test -f \"$dir/Hallo\"; then
echo \"$dir\"
break
fi
done`"
test -n "$NEW_DIR" && cd "$NEW_DIR"
Das 2. Beispiel könnte man ggf. auch über Kombinationen von find-Optionen realisieren, aber als Demonstration des Prinzips sollte es reichen.
Eine andere, oft genutzte Möglichkeit ist das Sourcen von Scripts. Ein beliebter Zweck der Anwendung ist das Einlesen von Konfigurationsdateien. Beim Sourcen von Scripts werden diese in der aktuellen Shell ausgeführt.
# Variante 1: In ein anderes Verzeichnis wechseln (chdir.sh ist das gleiche wie oben) jan@jack:~/tmp/subshell> . ./chdir.sh /home/jan/tmp/subshell/subdir jan@jack:~/tmp/subshell/subdir> pwd /home/jan/tmp/subshell/subdir # Variante 2: Konfigdatei einlesen jan@jack:~/tmp> cat config.cfg MY_VAR1=Hallo MY_VAR2=Welt jan@jack:~/tmp> source ./config.cfg jan@jack:~/tmp> echo "$MY_VAR1 $MY_VAR2" Hallo Welt
So, jetzt habe ich endlich auch mein eigenes Hello world-Beispiel ;-) Wie Ihr seht, ist der Punkt nur eine andere Variante von source. Beim Sourcen von Scripts müsst Ihr einen wichtigen Punkt beachten: Wenn das zu sourcende Script mit exit endet, dann ist auch Eure aktuelle Shell zu Ende!
Auch in der folgenden Situation müsst Ihr das Verhalten von Subshells nach einer Pipe beachten:
# Funktion, die nacheinander Dateien kopiert und ggf. umbenennt
# Parameter 1: Quellverzeichnis
# Parameter 2: Zielverzeichnis
# Parameter 3: Dateiendung der zu kopierenden Dateien
# Parameter 4: temporaere Dateiendung (optional)
# Returncode: 0 = OK
# 1 = Fehler
function copy_and_rename_files {
# Dateien suchen
find "$1" -name "*.$3" -type f -print | while read fname; do
# Zieldateiname
dstfile="$2/`basename \"$fname\"`"
# Zieldatei fuer Umbenennen
mv_file=
# wenn temp. Endung, dann an Zielnamen anhaengen, $mv_file setzen
if test -n "$4"; then
mv_file="$dstfile"
dstfile="${dstfile}.$4"
fi
# Kopieren, bei einem Fehler Schleife abbrechen
if ! cp "$fname" "$dstfile" 2>dev/null; then
# Fehlermeldung nach stderr
echo "Fehler beim Kopieren von '$fname'" >&2
return 1 # Vorsicht! Beendet nur die Subshell (Schleife), nicht die Funktion!
fi
# wenn temp. Endung, dann jetzt umbenennen
# wir gehen mal davon aus, dass das gutgeht, wenn das Kopieren
# geklappt hat
test -n "$mv_file" && mv "$dstfile" "$mv_file"
# noch eine Falle: Wenn nicht umbenannt wird, dann ist im letzten
# Schleifendurchlauf das Ergebnis des test der Returncode der
# Schleife, also falsch (1)
# zu verhindern mit einer Dummy-Anweisung, die immer wahr ist
true
done
# jetzt muss der Returncode der Schleife ausgewertet werden
return $?
}
Das letzte Beispiel war etwas ausführlicher, in die oben beschriebenen Fallen bin ich aber selbst schon getappst. Deshalb habe ich es mal in voller Schönheit erklärt.

