Come rilevare un errore in uno script bash di Linux?

Ho creato il seguente script:

# !/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" 

Lo script funziona, ma a parte il mio echi, cè anche loutput quando

cd $1 

fallisce durante lesecuzione.

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

È possibile prenderlo?

Commenti

  • Per tua informazione, puoi anche farlo molto più semplice; test -d /path/to/directory (o [[ -d /path/to/directory ]] in bash) ti dirà se un determinato target è o meno una directory e lo farà in silenzio.
  • @Patrick, che verifica solo se ‘ è una directory, non se puoi cd al suo interno.
  • @StephaneChazelas sì. Il nome della funzione è directoryExists.
  • Vedi una risposta dettagliata qui: Genera errore in uno script Bash .

Risposta

Lo script cambia le directory durante lesecuzione, il che significa che non funzionerà con una serie di nomi di percorso relativi. In seguito hai commentato che volevi solo controllare lesistenza della directory, non la possibilità di utilizzare cd, quindi le risposte non devono utilizzare cd affatto. Revisionato. Utilizzando tput e i colori di 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 } 

(modificato per utilizzare il più invulnerabile printf invece del problematico echo che potrebbe agire sulle sequenze di escape nel testo.)

Commenti

  • Questo risolve anche (a meno che xpg_echo non sia attivo) i problemi quando i nomi dei file contengono caratteri backslash.

Risposta

Usa set -e per impostare la modalità di uscita in caso di errore: se un semplice comando restituisce uno stato diverso da zero (che indica un errore), la shell si chiude.

Attenzione che set -e non si attiva sempre. I comandi nelle posizioni di test possono fallire (ad es. if failing_command, failing_command || fallback). I comandi nella subshell portano solo alluscita dalla subshell, non dalla genitore: set -e; (false); echo foo visualizza foo.

In alternativa o in aggiunta, in bash (e ksh e zsh, ma non semplice sh), puoi specificare un comando che viene eseguito nel caso in cui un comando restituisca uno stato diverso da zero, con la trappola ERR, ad es. trap "err=$?; echo >&2 "Exiting on error $err"; exit $err" ERR. Nota che in casi come (false); …, la trap ERR viene eseguita nella subshell, quindi “non può far uscire il genitore.

Commenti

  • Recentemente ho sperimentato un po e ho scoperto un modo conveniente per correggere il comportamento ||, che consente di gestire facilmente gli errori senza utilizzare trap. Vedere la mia risposta . Cosa ne pensi di questo metodo?
  • @ sam.kozin I don ‘ Non ho tempo per esaminare la tua risposta in dettaglio, in linea di principio sembra buona. A parte la portabilità, quali sono i vantaggi rispetto a ksh / bash / zsh ‘ s ERR trap?
  • Probabilmente lunico vantaggio è la componibilità, poiché ‘ non rischi di sovrascrivere unaltra trappola impostata prima che la funzione venga eseguita. Questa è una funzione utile quando ‘ riscrive alcune funzioni comuni che in seguito utilizzerai da altri script. Un altro vantaggio potrebbe essere la piena compatibilità POSIX, sebbene non sia così importante in quanto lo pseudo-segnale ERR è supportato in tutte le principali shell. Grazie per la recensione! =)
  • @ sam.kozin Ho dimenticato di scrivere nel mio commento precedente: potresti postare questo su Code Review e pubblicare un link nella chatroom .
  • Grazie per il suggerimento, ‘ cercherò di seguire esso. ‘ non sapevo di Code Review.

Risposta

Per espandere la @Gilles “risposta :

In effetti, set -e non funziona allinterno dei comandi se usi loperatore || dopo di essi, anche se li esegui in una subshell; ad esempio, questo non funzionerebbe:

 #!/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  

Ma è necessario per impedire il ritorno dalla funzione esterna prima della pulizia.

Cè un piccolo trucco che può essere usato per risolvere questo problema: esegui il comando interno in background e poi immediatamente aspettalo.Il wait integrato restituirà il codice di uscita del comando interno e ora “stai utilizzando || dopo wait, non la funzione interna, quindi set -e funziona correttamente allinterno di questultima:

 #!/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  

Ecco la funzione generica che si basa su questa idea. Dovrebbe funzionare in tutte le shell compatibili con POSIX se rimuovi local parole chiave, ovvero sostituire tutte le local x=y solo con 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 }  

Esempio di utilizzo:

 #!/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 "$@"  

Esecuzione dellesempio:

 $ ./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  

Il lunica cosa di cui devi essere consapevole quando usi questo metodo è che tutte le modifiche delle variabili Shell fatte dal com Il mandato passato a run non si propagherà alla funzione chiamante, perché il comando viene eseguito in una subshell.

Risposta

Non dici cosa intendi esattamente per catch — segnala e continua; interrompere lulteriore elaborazione?

Poiché cd restituisce uno stato diverso da zero in caso di errore, è possibile:

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

Potresti semplicemente uscire in caso di errore:

cd -- "$1" || exit 1 

Oppure, ripetere il tuo messaggio ed uscire:

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

E / o elimina lerrore fornito da cd in caso di errore:

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

Per standard, i comandi dovrebbero inserire messaggi di errore su STDERR (descrittore di file 2). Pertanto 2>/dev/null dice di reindirizzare STDERR al “bit-bucket” noto da /dev/null.

(non dimenticare per citare le tue variabili e contrassegnare la fine delle opzioni per cd).

Commenti

  • @Stephane Il punto di Chazelas di citare e segnalare la fine delle opzioni è stato ben preso. Grazie per la modifica.

Risposta

In realtà per il tuo caso direi che la logica può essere migliorata.

Invece di cd e poi controlla se esiste, controlla se esiste poi vai nella directory.

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

Ma se il tuo scopo è mettere a tacere i possibili errori, allora cd -- "$1" 2>/dev/null, ma questo ti renderà il debug più difficile in futuro. Puoi controllare il test if contrassegna a: Bash if documentation :

Comments

  • Questa risposta non riesce cita la variabile $1 e fallirà se quella variabile contiene spazi vuoti o altri metacaratteri della shell. Inoltre non riesce a verificare se lutente dispone dellautorizzazione per cd al suo interno.
  • Stavo effettivamente cercando di verificare se esisteva una determinata directory, non necessariamente con il cd . Ma poiché ‘ non ne sapevo di più, ho pensato che provare a registrarlo avrebbe causato un errore se non esistesse, quindi perché non prenderlo? Non ‘ non sapevo se [-d $ 1] fosse ‘ esattamente quello di cui avevo bisogno. Quindi, grazie mille! (I ‘ m utilizzato per proramare Java e verificare la presenza di una directory in unistruzione if non è esattamente comune in Java)

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *