Exception Handling bei Shell-Skripten (bash o. ä.)

Hi allerseits,

ich hätte da 'ne ganz dringende Frage bzgl. Fehlerbehandlung bei Shellskripten, vielleicht hab ich ja Glück und Ihr könnt mir dabei helfen:

Angenommen ich schreib 'n Shell-Skript, das per Cronjob jeden Abend irgend’nen ganz wichtigen Task erledigt und dabei würde, wenn irgendwas schief gehen sollte 'n gaaaanz riesiger Schaden entstehen und von daher muss wirklich 100%ig sicher gestellt sein, dass auf ALLE Fehler adäquat reagiert wird.

Zur Vereinfachung gelte folgendes:

Das Skript sei 1000 Zeilen lang.
Sollte in irgendeiner beliebigen Zeile irgendwas schief gehen, dann reicht es, wenn irgend’ne Funktion namens „fail“ aufgerufen wird, die sich um den Rest kümmert, es ist nur GANZ WICHTIG, dass auch wirklich bei jedem noch so winzig kleinen Fehler die Funktion ‚fail‘ aufgerufen wird.

Das Skritp sieht beispielsweise etwa so aus:

cmd1
cmd2 | cmd3 | cmd4
cmd5

Wie schaff ich es auf möglichst ohne viel zuätzliche Schreibarbeit, dass wirklich JEDER einzelne Befehl auf „return code==0“ getestet wird???

Super wäre was in der Art hier:

try {
cmd1
cmd2 | cmd3 | cmd4
cmd5

}
catch
{
fail
}

aber das geht ja leider nicht in bash oder ähnlichem.

Eine etwas schreibaufwendige Methode wäre folgende, aber leider gibt’s da beim Pipen Probleme, weil man da immer den Returncode des letzten Befehls zurückbekommt - zumindest in der bash:

cmd1 || fail
cmd2 || fail
cmd2 || fail
cmd3 | cmd4 | cmd5 || fail #ignoriert leider Fehler bei cmd3 oder cmd4
cmd6 || fail

Habt Ihr vielleicht irgendwelche Vorschläge???
Mir wäre auch schon etwas geholfen, wenn mir jemand sagen könnte ob und wie es geht, dass beim Pipen auch ein Fehlercode irgendwie mit „durchgeschleust“ wird.

Ganz herzlichen Dank schon im Voraus
Eure Natascha

PS: Übrigens, der Ansatz, „einfach auf die Fehlerausgabe achten und als Indiz für etwaig aufgetretene Fehler verwenden“ scheidet aus.

Hi allerseits,

Hallo Eineseits,

ich hätte da 'ne ganz dringende Frage bzgl. Fehlerbehandlung
bei Shellskripten, vielleicht hab ich ja Glück und Ihr könnt
mir dabei helfen:

Angenommen ich schreib 'n Shell-Skript, das per Cronjob jeden
Abend irgend’nen ganz wichtigen Task erledigt und dabei würde,
wenn irgendwas schief gehen sollte 'n gaaaanz riesiger Schaden
entstehen und von daher muss wirklich 100%ig sicher gestellt
sein, dass auf ALLE Fehler adäquat reagiert wird.

Zur Vereinfachung gelte folgendes:

Das Skript sei 1000 Zeilen lang.
Sollte in irgendeiner beliebigen Zeile irgendwas schief gehen,
dann reicht es, wenn irgend’ne Funktion namens „fail“
aufgerufen wird, die sich um den Rest kümmert, es ist nur GANZ
WICHTIG, dass auch wirklich bei jedem noch so winzig kleinen
Fehler die Funktion ‚fail‘ aufgerufen wird.

Und die Funktion fail tut dann immer genau das gleiche?
Was tut Sie denn - Dich informieren?

Das Skritp sieht beispielsweise etwa so aus:

cmd1
cmd2 | cmd3 | cmd4
cmd5

Wie schaff ich es auf möglichst ohne viel zuätzliche
Schreibarbeit, dass wirklich JEDER einzelne Befehl auf „return
code==0“ getestet wird???

Super wäre was in der Art hier:

try {
cmd1
cmd2 | cmd3 | cmd4
cmd5

}
catch
{
fail
}

aber das geht ja leider nicht in bash oder ähnlichem.

doch. Starte ein Perl-Skript mit cron. Perl unterstützt solche Konstrukte.

Eine etwas schreibaufwendige Methode wäre folgende, aber
leider gibt’s da beim Pipen Probleme, weil man da immer den
Returncode des letzten Befehls zurückbekommt - zumindest in
der bash:

cmd1 || fail
cmd2 || fail
cmd2 || fail
cmd3 | cmd4 | cmd5 || fail #ignoriert leider Fehler bei cmd3
oder cmd4
cmd6 || fail

Habt Ihr vielleicht irgendwelche Vorschläge???
Mir wäre auch schon etwas geholfen, wenn mir jemand sagen
könnte ob und wie es geht, dass beim Pipen auch ein Fehlercode
irgendwie mit „durchgeschleust“ wird.

Ganz herzlichen Dank schon im Voraus
Eure Natascha

PS: Übrigens, der Ansatz, „einfach auf die Fehlerausgabe
achten und als Indiz für etwaig aufgetretene Fehler verwenden“
scheidet aus.

Du kannst den Fehlerkanal auf eine .log-Datei umbiegen. Und am Ende deines Skriptes prüfst Du ob dieses leer ist. Wenn nicht, gibt’s ne mail an Dich.
Oder ist genau das, was ausscheidet?
Viele Grüße
Denis

Hi allerseits,

Hallo,

Quatsch perl, „Fehlerkanal“, alles Kaese…

Das Skript sei 1000 Zeilen lang.
Sollte in irgendeiner beliebigen Zeile irgendwas schief gehen,
dann reicht es, wenn irgend’ne Funktion namens „fail“
aufgerufen wird, die sich um den Rest kümmert, es ist nur GANZ
WICHTIG, dass auch wirklich bei jedem noch so winzig kleinen
Fehler die Funktion ‚fail‘ aufgerufen wird.

 $ man trap

Eine etwas schreibaufwendige Methode wäre folgende, aber
leider gibt’s da beim Pipen Probleme, weil man da immer den
Returncode des letzten Befehls zurückbekommt - zumindest in
der bash:

cmd1 || fail
cmd2 || fail
cmd2 || fail
cmd3 | cmd4 | cmd5 || fail #ignoriert leider Fehler bei cmd3 oder cmd4

Dann schreib’s halt anders hin:

 (cmd3 || fail) | (cmd4 || fail) | (cmd5 || fail)

Habt Ihr vielleicht irgendwelche Vorschläge???
Mir wäre auch schon etwas geholfen, wenn mir jemand sagen
könnte ob und wie es geht, dass beim Pipen auch ein Fehlercode
irgendwie mit „durchgeschleust“ wird.

Siehe oben: nicht durchschleusen, sondern sofort behandeln. Aber eigentlich willst Du trap verwenden:

(0) frank@harbard [~] % cat foo.sh
#!/bin/bash

trap 'echo "Allgemeine Schutzverletzung in Modul $0 von $SHELL [Details]"; exit 1' ERR

touch /foobar

echo "Das kaeme nach dem Fehler."
(0) frank@harbard [~] % ./foo.sh
touch: cannot touch `/foobar': Permission denied
Allgemeine Schutzverletzung in Modul ./foo.sh von /bin/zsh [Details]
(1) frank@harbard [~] % 

HTH,
Gruss vom Frank.

Hi Denis,

erst ma Danke für Deine schnellle Antwort …

Und die Funktion fail tut dann immer genau das gleiche?
Was tut Sie denn - Dich informieren?

Ja, bissi aufräumen und dann 'ne Infomail an mich schicken.

doch. Starte ein Perl-Skript mit cron. Perl unterstützt solche
Konstrukte.

Okay, war meinerseits schlecht formuliert: mit „bash oder ähnlichem“ war eher „bash oder ähnliche shell“ gemeint … nichtsdestotrotz werd ich in Zukunft wohl wirklich eher auf „perl o.ä.“ setzen … ungeachtet von meinen Problemchen hier, geht halt nichts über 'ne ordentliche Programmiersprache

Du kannst den Fehlerkanal auf eine .log-Datei umbiegen. Und am
Ende deines Skriptes prüfst Du ob dieses leer ist. Wenn nicht,
gibt’s ne mail an Dich.
Oder ist genau das, was ausscheidet?

Ja, exakt das meinte ich in bezug auf „ausscheiden“ :smile:

Thanks a lot
Natascha

Hi Frank,

1000 Dank, das mit dem „trap … ERR“ war prinzipiell genau das, was ich gesucht hab … aber leider steckt der Wurm doch noch bissi im Detail.

'n ziemliches Problem ist einfach, dass durch das Pipen 'ne Subprozess aufgemacht wird, der doch alles etwas schwierig macht.

Hier 'n Beispielskript (bitte beachten, dass „grep“, wenn nichts matched 'n Fehlercode 1 zurückgibt - das soll im folgenden den „Fehlerfall“ darstellen):

#!/bin/sh

fail() { echo „FAILED!!!“; exit 1; }

trap ‚echo TRAPPED ERROR!!!; exit 1‘ ERR
echo xxx | (grep yyy || fail) | cat
sleep 1
echo „Hier haette ich eigentlich nie hinkommen sollen/wollen“

Das Skript zeigt zwei Dinge:

Beim fehlererzeugenden „grep“ wird NICHT die mittels „trap“ angegebene Fehlerbearbeitung angesprungen.
Das ‚|| fail‘ führt zwar dazu, dass „fail“ tatsächlich aufgerufen wird, allerdings schliesst „exit 1“ nur den Subprozess, der Hauptprozess läuft leider weiter.

Irgendwelche Ideen/Anmerkungen/Korrekturen etc.???

Ganz lieben Dank für Deine Hilfe

Hugs
Natascha

Moien

'n ziemliches Problem ist einfach, dass durch das Pipen 'ne
Subprozess aufgemacht wird, der doch alles etwas schwierig
macht.

Nicht wirklich. Das Problem bei deinem Aufbau ist der Aufruf fail. Teste mal das:

#!/bin/sh

trap 'echo TRAPPED ERROR!!!; exit 1' ERR

echo xxx | grep xx | tee -a testing | grep yyy
sleep 1
echo "Hier haette ich eigentlich nie hinkommen sollen/wollen"

cu

Moien

Moin moin :smile:

'n ziemliches Problem ist einfach, dass durch das Pipen 'ne
Subprozess aufgemacht wird, der doch alles etwas schwierig
macht.

Nicht wirklich. Das Problem bei deinem Aufbau ist der Aufruf
fail. Teste mal das:

#!/bin/sh

trap ‚echo TRAPPED ERROR!!!; exit 1‘ ERR

echo xxx | grep xx | tee -a testing | grep yyy
sleep 1
echo „Hier haette ich eigentlich nie hinkommen sollen/wollen“

Na ja, vielleicht hab ich Dich da ja auch nur falsch verstanden, aber wenn ich hinter die Pipe-Schlange noch ein " | cat" anhäng wird wieder nicht „getrapped“ …

also:
echo xxx | grep xx | tee -a testing | grep yyy | cat

Oder?

Thanks
Natascha

Wenn das Script so lang und soooo wichtig ist, sollte man statt Shell-Script vielleicht eine Sprache wählen, die einem dieses Fehlerabfangen erleichtert… :wink:

Folgendes könnte evtl. funktionieren:

cmd1 && cmd2 && cmd3

cmdfail

Die beiden „&&“ bewirken, daß das folgende Kommando nur dann ausgefüllt wird, wenn das erste Kommando mit Return-Code 0 beendet wurde.

Hin oder her, schreibaufwendig wird das in der Bash bleiben, da sie keine wirkliche Fehlerbehandlung kennt.
Du kannst Dir ja z.B. selbst die Funktion „try“ schreiben und dann jeden auszuführenden Befehl an diese Funktion übergeben. Aber weniger Schreibarbeit ist das auch nicht.

viele Grüße

Falko

[Bei dieser Antwort wurde das Vollzitat nachträglich automatisiert entfernt]