Wie kann man einen Fehler in einem Linux-Bash-Skript abfangen?

Ich habe das folgende Skript erstellt:

# !/bin/bash # OUTPUT-COLORING red="\e[0;31m" green="\e[0;32m" NC="\e[0m" # No Color # FUNCTIONS # directoryExists - Does the directory exist? function directoryExists { cd $1 if [ $? = 0 ] then echo -e "${green}$1${NC}" else echo -e "${red}$1${NC}" fi } # EXE directoryExists "~/foobar" directoryExists "/www/html/drupal" 

Das Skript funktioniert, aber neben meinem Echos, es gibt auch die Ausgabe, wenn

cd $1 

bei der Ausführung fehlschlägt.

testscripts//test_labo3: line 11: cd: ~/foobar: No such file or directory 

Ist es möglich, dies zu erfassen?

Kommentare

  • Nur zu Ihrer Information, Sie können dies auch viel einfacher tun; test -d /path/to/directory (oder [[ -d /path/to/directory ]] in bash) zeigt Ihnen an, ob ein bestimmtes Ziel ein Verzeichnis ist oder nicht, und tut dies leise.
  • @Patrick, der nur testet, ob es sich um ein ‚ Verzeichnis handelt, nicht darum, ob Sie cd in dieses Verzeichnis einfügen können.
  • @StephaneChazelas ja. Der Funktionsname lautet directoryExists.
  • Eine ausführliche Antwort finden Sie hier: Fehler in einem Bash-Skript auslösen .

Antwort

Ihr Skript ändert während der Ausführung die Verzeichnisse, was bedeutet, dass es nicht funktioniert Eine Reihe relativer Pfadnamen. Sie haben später kommentiert, dass Sie nur die Existenz eines Verzeichnisses überprüfen möchten, nicht die Möglichkeit, cd zu verwenden. Antworten müssen also nicht cd überhaupt. Überarbeitet. Verwenden von tput und Farben aus man terminfo:

#!/bin/bash -u # OUTPUT-COLORING red=$( tput setaf 1 ) green=$( tput setaf 2 ) NC=$( tput setaf 0 ) # or perhaps: tput sgr0 # FUNCTIONS # directoryExists - Does the directory exist? function directoryExists { # was: do the cd in a sub-shell so it doesn"t change our own PWD # was: if errmsg=$( cd -- "$1" 2>&1 ) ; then if [ -d "$1" ] ; then # was: echo "${green}$1${NC}" printf "%s\n" "${green}$1${NC}" else # was: echo "${red}$1${NC}" printf "%s\n" "${red}$1${NC}" # was: optional: printf "%s\n" "${red}$1 -- $errmsg${NC}" fi } 

(bearbeitet) Verwenden Sie das unverwundbarere printf anstelle des problematischen echo, das möglicherweise auf Escape-Sequenzen im Text wirkt.)

Kommentare

  • Damit werden auch die Probleme behoben (sofern xpg_echo nicht aktiviert ist), wenn Dateinamen Backslash-Zeichen enthalten.

Antwort

Verwenden Sie set -e, um den Exit-on-Error-Modus festzulegen: Wenn ein einfacher Befehl einen Status ungleich Null zurückgibt (was auf einen Fehler hinweist), Die Shell wird beendet.

Beachten Sie, dass set -e nicht immer aktiviert wird. Befehle in Testpositionen dürfen fehlschlagen (z. B. if failing_command, failing_command || fallback). Befehle in der Unterschale führen nur zum Verlassen der Unterschale, nicht der übergeordneten: set -e; (false); echo foo zeigt foo.

Alternativ oder zusätzlich in bash (und ksh und zsh, aber nicht einfach sh), können Sie einen Befehl angeben, der ausgeführt wird, falls ein Befehl einen Status ungleich Null zurückgibt, mit der ERR -Falle, z. trap "err=$?; echo >&2 "Exiting on error $err"; exit $err" ERR. Beachten Sie, dass in Fällen wie (false); … der ERR-Trap in der Subshell ausgeführt wird, sodass das übergeordnete Element nicht beendet werden kann.

Kommentare

  • Vor kurzem habe ich ein wenig experimentiert und eine bequeme Methode zur Behebung des || -Verhaltens gefunden, mit der sich Fehler ohne Traps problemlos richtig behandeln lassen meine Antwort . Was halten Sie von dieser Methode?
  • @ sam.kozin Ich habe ‚ Sie haben keine Zeit, Ihre Antwort im Detail zu überprüfen. Sie sieht im Prinzip gut aus. Abgesehen von der Portabilität, was sind die Vorteile gegenüber der ERR-Falle von ksh / bash / zsh ‚?
  • Wahrscheinlich ist der einzige Vorteil die Kompositionsfähigkeit, da Sie ‚ nicht riskieren, einen anderen Trap zu überschreiben, der vor der Ausführung der Funktion festgelegt wurde. Dies ist eine nützliche Funktion, wenn Sie ‚ schreibt eine allgemeine Funktion, die Sie später aus anderen Skripten beziehen und verwenden werden. Ein weiterer Vorteil Möglicherweise ist die POSIX-Kompatibilität vollständig, obwohl dies nicht so wichtig ist, da das Pseudosignal ERR in allen wichtigen Shells unterstützt wird. Danke für die Bewertung! =)
  • @ sam.kozin Ich habe vergessen, in meinem vorherigen Kommentar zu schreiben: Vielleicht möchten Sie dies auf Code Review posten und a Link im Chatroom .
  • Vielen Dank für den Vorschlag, ich werde versuchen, ‚ zu folgen es. Wusste ‚ nichts über Code Review.

Antwort

So erweitern Sie die Antwort @ Gilles :

In der Tat funktioniert set -e nicht Inside-Befehle, wenn Sie den Operator || nach ihnen verwenden, auch wenn Sie sie in einer Subshell ausführen; Dies würde beispielsweise nicht funktionieren:

 #!/bin/sh # prints: # # --> outer # --> inner # ./so_1.sh: line 16: some_failed_command: command not found # <-- inner # <-- outer set -e outer() { echo "--> outer" (inner) || { exit_code=$? echo "--> cleanup" return $exit_code } echo "<-- outer" } inner() { set -e echo "--> inner" some_failed_command echo "<-- inner" } outer  

Aber wird benötigt, um zu verhindern, dass vor der Bereinigung von der äußeren Funktion zurückgekehrt wird.

Es gibt einen kleinen Trick, mit dem dies behoben werden kann: Führen Sie den inneren Befehl im Hintergrund und dann sofort aus warte darauf.Das eingebaute wait gibt den Exit-Code des inneren Befehls zurück, und jetzt verwenden Sie || nach , nicht die innere Funktion, daher funktioniert set -e in letzterer ordnungsgemäß:

 #!/bin/sh # prints: # # --> outer # --> inner # ./so_2.sh: line 27: some_failed_command: command not found # --> cleanup set -e outer() { echo "--> outer" inner & wait $! || { exit_code=$? echo "--> cleanup" return $exit_code } echo "<-- outer" } inner() { set -e echo "--> inner" some_failed_command echo "<-- inner" } outer  

Hier ist die generische Funktion, die auf dieser Idee aufbaut. Sie sollte in allen POSIX-kompatiblen Shells funktionieren, wenn Sie local Schlüsselwörter, dh ersetzen Sie alle local x=y durch nur x=y:

 # [CLEANUP=cleanup_cmd] run cmd [args...] # # `cmd` and `args...` A command to run and its arguments. # # `cleanup_cmd` A command that is called after cmd has exited, # and gets passed the same arguments as cmd. Additionally, the # following environment variables are available to that command: # # - `RUN_CMD` contains the `cmd` that was passed to `run`; # - `RUN_EXIT_CODE` contains the exit code of the command. # # If `cleanup_cmd` is set, `run` will return the exit code of that # command. Otherwise, it will return the exit code of `cmd`. # run() { local cmd="$1"; shift local exit_code=0 local e_was_set=1; if ! is_shell_attribute_set e; then set -e e_was_set=0 fi "$cmd" "$@" & wait $! || { exit_code=$? } if [ "$e_was_set" = 0 ] && is_shell_attribute_set e; then set +e fi if [ -n "$CLEANUP" ]; then RUN_CMD="$cmd" RUN_EXIT_CODE="$exit_code" "$CLEANUP" "$@" return $? fi return $exit_code } is_shell_attribute_set() { # attribute, like "x" case "$-" in *"$1"*) return 0 ;; *) return 1 ;; esac }  

Anwendungsbeispiel:

 #!/bin/sh set -e # Source the file with the definition of `run` (previous code snippet). # Alternatively, you may paste that code directly here and comment the next line. . ./utils.sh main() { echo "--> main: $@" CLEANUP=cleanup run inner "$@" echo "<-- main" } inner() { echo "--> inner: $@" sleep 0.5; if [ "$1" = "fail" ]; then oh_my_god_look_at_this fi echo "<-- inner" } cleanup() { echo "--> cleanup: $@" echo " RUN_CMD = "$RUN_CMD"" echo " RUN_EXIT_CODE = $RUN_EXIT_CODE" sleep 0.3 echo "<-- cleanup" return $RUN_EXIT_CODE } main "$@"  

Ausführen des Beispiels:

 $ ./so_3 fail; echo "exit code: $?" --> main: fail --> inner: fail ./so_3: line 15: oh_my_god_look_at_this: command not found --> cleanup: fail RUN_CMD = "inner" RUN_EXIT_CODE = 127 <-- cleanup exit code: 127 $ ./so_3 pass; echo "exit code: $?" --> main: pass --> inner: pass <-- inner --> cleanup: pass RUN_CMD = "inner" RUN_EXIT_CODE = 0 <-- cleanup <-- main exit code: 0  

Die Das einzige, was Sie bei der Verwendung dieser Methode beachten müssen, ist, dass alle Änderungen an Shell-Variablen über die com vorgenommen werden Wenn Sie an run übergeben, wird dies nicht an die aufrufende Funktion weitergegeben, da der Befehl in einer Unterschale ausgeführt wird.

Antwort

Sie sagen nicht, was genau Sie mit catch meinen — melden und fortfahren; Weitere Verarbeitung abbrechen?

Da cd bei einem Fehler einen Status ungleich Null zurückgibt, können Sie Folgendes tun:

cd -- "$1" && echo OK || echo NOT_OK 

Sie können bei einem Fehler einfach beenden:

cd -- "$1" || exit 1 

Oder geben Sie Ihre eigene Nachricht wieder und beenden Sie:

cd -- "$1" || { echo NOT_OK; exit 1; } 

Und / oder unterdrücken Sie den Fehler, den cd bei einem Fehler verursacht:

cd -- "$1" 2>/dev/null || exit 1 

Standardmäßig sollten Befehle Fehlermeldungen auf STDERR (Dateideskriptor 2) setzen. Daher sagt 2>/dev/null, dass STDERR an den „Bit-Bucket“ weitergeleitet wird, der unter /dev/null bekannt ist.

(nicht vergessen um Ihre Variablen zu zitieren und das Ende der Optionen für cd) zu markieren.

Kommentare

  • @Stephane Chazelas Punkt des Zitierens und Signalisierens des Endes der Optionen ist gut getroffen. Vielen Dank für die Bearbeitung.

Antwort

Eigentlich Für Ihren Fall würde ich sagen, dass die Logik verbessert werden kann.

Anstelle von CD und dann prüfen, ob es existiert, prüfen Sie, ob es existiert, und gehen Sie dann in das Verzeichnis.

if [ -d "$1" ] then printf "${green}${NC}\\n" "$1" cd -- "$1" else printf "${red}${NC}\\n" "$1" fi 

Wenn Sie jedoch die möglichen Fehler zum Schweigen bringen möchten, cd -- "$1" 2>/dev/null, wird dies das Debuggen in Zukunft erschweren. Sie können den if-Test überprüfen Flags bei: Bash, wenn Dokumentation :

Kommentare

  • Diese Antwort schlägt fehl Zitieren Sie die Variable $1 und schlagen fehl, wenn diese Variable enthält Leerzeichen oder andere Shell-Metazeichen. Es kann auch nicht überprüft werden, ob der Benutzer die Berechtigung hat, cd darin einzugeben.
  • Ich habe tatsächlich versucht zu überprüfen, ob ein bestimmtes Verzeichnis vorhanden ist, nicht unbedingt eine CD darauf . Aber weil ich ‚ es nicht besser wusste, dachte ich, dass der Versuch, eine CD zu erstellen, einen Fehler verursachen würde, wenn er nicht existiert. Warum also nicht abfangen? Ich wusste ‚ nicht, ob d $ 1] ‚ genau das ist, was ich brauchte. Also vielen Dank! (Ich ‚ werde verwendet, um Java zu programmieren und nach einem Verzeichnis in einer if-Anweisung zu suchen, das in Java nicht genau üblich ist.)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.