10.3 Fehlersuche
Wenn der Fehler aufgetreten ist, dann bietet Ihnen die Shell einige Optionen, die Ihnen bei der Fehlersuche helfen. Aber egal, welche Fehler denn nun aufgetreten sind, als Erstes sollten Sie die Fehlermeldung lesen und auch verstehen können. Plausibel, aber leider werden immer wieder Fragen gestellt, warum dies oder jenes falsch läuft, obwohl die Antwort zum Teil schon eindeutig der Fehlermeldung zu entnehmen ist. Ganz klar im Vorteil ist hier derjenige, der das Buch durchgearbeitet, die Scripts abgetippt und ausprobiert hat. Durch »Trial and error« hat derjenige eine Menge Fehler produziert, aber auch gelernt, wann welche Fehlermeldung ausgegeben wird.
10.3.1 Tracen mit set -x
Der Trace-Modus (trace = verfolgen, untersuchen), den man mit set –x setzt, haben Sie schon des Öfteren in diesem Buch eingesetzt, etwa als es darum ging, zu sehen, wie das Script bzw. die Befehle ablaufen. Die Option set –x wurde bereits in Abschnitt 1.8.9 ausführlich behandelt. Trotzdem muss noch erwähnt werden, dass die Verwendung der Trace-Option nur in der aktuellen Shell und den Subshells aktiv ist. Zum Beispiel wollen Sie wissen, was beim folgenden Script passiert:
# Name: areweroot
if [ $UID = 0 ]
then
echo "Wir sind root!"
renice –5 $$
else
echo "Wir sind nicht root!"
su -c 'renice –5 $$'
fi
Wenn Sie das Script jetzt tracen wollen, genügt es nicht, einfach vor seiner Verwendung die Option –x zu setzen:
you@host > ./areweroot
+ ./areweroot
Wir sind nicht root!
Password:********
8967: Alte Priorität: 0, neue Priorität: –5
Das ist ein häufiges Missverständnis. Sie müssen die Option selbstverständlich an der entsprechenden Stelle (oder am Anfang des Scripts) setzen:
# Name: areweroot2
# Trace-Modus einschalten
set -x
if [ $UID = 0 ]
then
echo "Wir sind root!"
renice –5 $$
else
echo "Wir sind nicht root!"
su -c 'renice –5 $$'
fi
Das Script bei der Ausführung:
you@host > ./areweroot2
+ ./areweroot
++ '[' 1000 = 0 ']'
++ echo 'Wir sind nicht root!'
Wir sind nicht root!
++ su -c 'renice –5 $$'
Password:*******
9050: Alte Priorität: 0, neue Priorität: –5
you@host > su
Password:********
# ./areweroot2
++ '[' 0 = 0 ']'
++ echo 'Wir sind root!'
Wir sind root!
++ renice –5 9070
9070: Alte Priorität: 0, neue Priorität: –5
Hinweis Alternativ bietet sich hier auch die Möglichkeit, die Option -x an das Script mit einem Shell-Aufruf zu übergeben, z. B. bash -x ./script oder ksh -x ./script, wodurch sich eine Modifikation am Script vermeiden lässt. Oder Sie verwenden die She-Bang-Zeile: #!/usr/bin/bash –x.
|
Häufig finden Sie mehrere Pluszeichen am Anfang einer Zeile. Dies zeigt an, wie tief die Verschachtelungsebene ist, in der die entsprechende Zeile ausgeführt wird. Jedes weitere Zeichen bedeutet eine Ebene tiefer. So verwendet beispielsweise folgendes Script drei Schachtelungsebenen:
# Name: datum
# Trace-Modus einschalten
set -x
datum=`date`
echo "Heute ist $datum
Das Script bei der Ausführung:
you@host > ./datum
+./script1
+++ date
++ datum=Fr Apr 1 10:26:25 CEST 2005
++ echo 'Heute ist Fr Apr 1 10:26:25 CEST 2005'
Heute ist Fr Apr 1 10:26:25 CEST 2005
10.3.2 DEBUG und ERR-Signal
Für Bash und Korn-Shell gibt es eine echte Debugging-Alternative mit dem DEBUG-Signal. Das ERR-Signal hingegen ist nur der Korn-Shell vorbehalten. Angewendet werden diese Signale genauso, wie Sie dies von den Signalen her kennen. Sie richten sich hierbei einen Handler mit trap ein.
trap 'Kommando(s)' DEBUG
# nur für die Korn-Shell
trap 'Kommando(s)' ERR
Im folgenden Beispiel haben wir ein Script, welches ständig in einer Endlosschleife läuft, aber wir sind zu blind, den Fehler zu erkennen:
# Name: debug1
val=1
while [ "$val" -le 10 ]
do
echo "Der ${val}. Schleifendurchlauf"
i=`expr $val + 1`
done
Jetzt wollen wir dem Script einen »Entwanzer« mit trap einbauen:
# Name: debug1
trap 'printf "$LINENO :-> " ; read line ; eval $line' DEBUG
val=1
while [ "$val" -le 10 ]
do
echo "Der ${val}. Schleifendurchlauf"
i=`expr $val + 1`
done
Beim Entwanzen wird gewöhnlich das Kommando eval verwendet, mit dem Sie im Script Kommandos so ausführen können, als wären diese Teil des Scripts (siehe Abschnitt 9.1).
trap 'printf "$LINENO :-> " ; read line ; eval $line' DEBUG
Mit DEBUG wird nach jedem Ausdruck ein DEBUG-Signal gesendet, welches Sie »trap(pen)« können, um eine bestimmte Aktion auszuführen. Im Beispiel wird zunächst die Zeilennummer des Scripts gefolgt von einem Prompt ausgegeben. Anschließend können Sie einen Befehl einlesen und mit eval ausführen lassen (bspw. Variablen erhöhen oder reduzieren, Datei(en) anlegen, löschen, verändern, auflisten, überprüfen etc). Das Script bei der Ausführung:
you@host > ./debug1
5 :->(ENTER)
7 :->(ENTER)
9 :->(ENTER)
Der 1. Schleifendurchlauf
10 :-> echo $val
1
7 :->(ENTER)
9 :->(ENTER)
Der 1. Schleifendurchlauf
10 :-> echo $val
1
7 :->(ENTER)
9 :->(ENTER)
Der 1. Schleifendurchlauf
10 :-> val=`expr $val + 1`
7 :-> echo $val
2
9 :->(ENTER)
Der 2. Schleifendurchlauf
10 :->(ENTER)
7 :->(ENTER)
9 :->(ENTER)
Der 2. Schleifendurchlauf
10 :-> exit
Der Fehler ist gefunden, die Variable »val« wurde nicht hochgezählt, und ein Blick auf die Zeile 10 zeigt Folgendes:
i=`expr $val + 1`
Hier haben wir »i« statt »val« verwendet. Etwas störend ist allerdings, dass nur die Zeilennummer ausgegeben wird. Bei längeren Scripts ist es schwer bzw. umständlich, die Zeilennummer parallel zum Debuggen zu behandeln. Daher macht es Sinn, wenn auch hier die entsprechende Zeile mit ausgegeben wird, die ausgeführt wird bzw. wurde. Dies ist im Grunde kein Problem, da hier die DEBUG-Signale nur in der Bash bzw. der Korn-Shell vorhanden sind, weshalb auch gleich das komplette Script in ein Array eingelesen werden kann. Für den Index verwenden Sie einfach wieder die Variable LINENO. Die Zeile eval zum Ausführen von Kommandos packen Sie einfach in eine separate Funktion, die beliebig viele Befehle aufnehmen kann, bis eben (ENTER) gedrückt wird.
# Name: debug2
# ------- DEBUG Anfang --------- #
# Die übliche eval-Funktion
debugging() {
printf "STOP > "
while true
do
read line
[ "$line" = "" ] && break
eval $line
printf " > "
done
}
typeset -i index=1
# Das komplette Script in ein Array einlesen
while read zeile[$index]
do
index=index+1
done<$0
trap 'echo "${zeile[$LINENO]}" ; debugging' DEBUG
# ------- DEBUG Ende --------- #
typeset -i val=1
while (( $val <= 10 ))
do
echo "Der $val Schleifendurchlauf"
val=val+1
done
Das Script bei der Ausführung:
you@host > ./debug2
typeset -i val=1
STOP >(ENTER)
while (( $val <= 10 ))
STOP > echo $val
1
> val=7
> echo $val
7
>(ENTER)
echo "Der $val Schleifendurchlauf"
STOP >(ENTER)
Der 7 Schleifendurchlauf
val=val+1
STOP >(ENTER)
while (( $val <= 10 ))
STOP >(ENTER)
echo "Der $val Schleifendurchlauf"
STOP >(ENTER)
Der 8 Schleifendurchlauf
val=val+1
STOP >(ENTER)
while (( $val <= 10 ))
STOP >(ENTER)
echo "Der $val Schleifendurchlauf"
STOP >(ENTER)
Der 9 Schleifendurchlauf
val=val+1
STOP >exit
you@host >
Hinweis Auch in der Bourne-Shell oder anderen Shells, die eben nicht das DEBUG-Signal unterstützen, können Sie die Funktion debugging() hinzufügen.
|
Nur müssen Sie diese Funktion mehrmals hinter oder/und vor den Zeilen einsetzen, von denen Sie vermuten, dass das Script nicht richtig funktioniert. Natürlich sollten Sie es nicht versäumen, dies aus Ihrem fertigen Script wieder zu entfernen.
|
In der Korn-Shell finden Sie des Weiteren das Signal ERR, welches Sie ebenfalls mit trap einfangen können. Allerdings handelt es sich hierbei eher um ein Pseudo-Signal, denn das Signal bezieht sich immer auf den Rückgabewert eines Kommandos. Ist dieser ungleich 0, wird das Signal ERR gesendet. Allerdings lässt sich dies nicht dort verwenden, wo bereits der Exit-Code eines Kommandos abgefragt wird (bspw. if, while ...). Auch hierzu ein simples Script, welches das Signal ERR abfängt:
# Name: debugERR
error_handling() {
echo "Fehler: $ERRNO Zeile: $LINENO"
printf "Beenden (j/n) : " ; read
[ "$REPLY" = "j" ] && exit 1
}
trap 'error_handling' ERR
echo "Testen des ERR-Signals"
# Sollte dem normalen Benutzer untersagt sein
cat > /etc/profile
echo "Nach dem Testen des ERR-Signals"
Das Script bei der Ausführung:
you@host > ksh ./debugERR
Testen des ERR-Signals
Fehler: Permission denied Zeile: 4
Beenden (j/n) : j
Hinweis Zwar wird die Bash beim Signal ERR in den Dokumentationen nicht erwähnt, aber beim Testen hat dies auch unter der Bash zu funktionieren, nur dass es eben in der Bash nicht die Variable ERRNO gibt.
|
10.3.3 Variablen und Syntax überprüfen
Um wirklich sicherzugehen, dass Sie nicht auf eine nicht gesetzte Variable zugreifen, können Sie set mit der Option –u verwenden. Wird hierbei auf eine nicht definierte Variable zugegriffen, wird eine entsprechende Fehlermeldung ausgegeben. Mit +u schalten Sie diese Option wieder ab.
# Name: aunboundvar
# Keine undefinierten Variablen zulassen
set –u
var1=100
echo $var1 $var2
Das Script bei der Ausführung:
you@host > ./aunboundvar
./aunboundvar: line 7: var2: unbound variable
Wollen Sie ein Script nicht ausführen, sondern nur dessen Syntax überprüfen lassen, können Sie die Option –n verwenden. Mit +n schalten Sie diese Option wieder aus.
10.3.4 Eine Debug-Ausgabe hinzufügen
Die primitivste Form aller Debugging-Techniken ist gleichzeitig wohl die meisteingesetzte Variante – nicht nur in der Shell-Programmierung. Sie setzen überall dort, wo Sie einen Fehler vermuten, eine echo-Ausgabe über den Zustand der Daten (bspw. welchen Wert hat eine Variable). Gibt das Script sehr viel auf dem Bildschirm aus, kann man auch einfach hie und da mal ein einfaches read einbauen, wodurch auf einen (ENTER)-Tastendruck gewartet wird, ehe die Ausführung des Scripts weiter fortfährt, oder man kann auch die ein oder andere störende Ausgabe kurzeitig ins Datengrab /dev/null schicken. Ein einfaches Beispiel:
# Name: maskieren
nobody() {
echo "Die Ausgabe wollen wir nicht!!!"
}
echo "Ausgabe1"
exec 1>/dev/null
nobody
exec 1>`tty`
echo "Ausgabe2"
Hier interessieren wir uns nicht für die Ausgabe der Funktion »nobody« und schicken die Standardausgabe ins Datengrab. Natürlich müssen Sie das Ganze wieder rückgängig machen. Hier erreichen wir dies mit einer Umleitung auf das aktuelle Terminal (das Kommando tty übernimmt das für uns).
10.3.5 Debugging-Tools
Wer ein fertiges Debugging-Tool sucht, dem seien für die Bash der Debugger bashdb (http://bashdb.sourceforge.net/) und für die Korn-Shell kshdb ans Herz gelegt. Der Bash-Debugger ist nichts anderes als eine gepatchte Version der Bash, welche ein besseres Debugging, ebenso wie eine verbesserte Fehlerausgabe unterstützt.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.
|