Cum se detectează o eroare într-un script Linux bash?

Am realizat următorul 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" 

Scriptul funcționează, dar lângă ecouri, există și ieșirea când

cd $1 

eșuează la executare.

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

Este posibil să prindem acest lucru?

Comentarii

  • Doar un FYI, puteți face acest lucru mult mai simplu; test -d /path/to/directory (sau [[ -d /path/to/directory ]] în bash) vă va spune dacă o anumită țintă este sau nu un director și o va face în liniște.
  • @Patrick, care testează doar dacă este ‘ un director, nu dacă puteți cd în el.
  • @StephaneChazelas da. Numele funcției este directoryExists.
  • Consultați aici un răspuns detaliat: Creșteți eroarea într-un script Bash .

Răspuns

Scriptul dvs. modifică directoarele pe măsură ce rulează, ceea ce înseamnă că nu va funcționa cu o serie de căi relative. Apoi ați comentat mai târziu că doriți doar să verificați existența directorului, nu abilitatea de a utiliza cd, astfel încât răspunsurile nu trebuie să utilizați cd deloc. Revizuit. Folosind tput și culorile din 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 } 

(Editat să folosiți printf mai invulnerabil în locul problematicii echo care ar putea acționa asupra secvențelor de evacuare din text.)

Comentarii

  • Acest lucru rezolvă, de asemenea, (cu excepția cazului în care xpg_echo este activat) problemele atunci când numele fișierelor conțin caractere de tip backslash.

Răspuns

Utilizați set -e pentru a seta modul exit-on-error: dacă o comandă simplă returnează o stare diferită de zero (indicând eșec), shell-ul iese.

Aveți grijă ca set -e să nu intre întotdeauna. Comenzile din pozițiile de testare sunt permise să eșueze (de ex. if failing_command, failing_command || fallback). Comenzile din subshell duc doar la ieșirea din subshell, nu părintele: set -e; (false); echo foo afișează foo.

Alternativ, sau în plus, în bash (și ksh și zsh, dar nu sh simplu), puteți specifica o comandă care se execută în cazul în care o comandă returnează o stare diferită de zero, cu capcana ERR, de ex. trap "err=$?; echo >&2 "Exiting on error $err"; exit $err" ERR. Rețineți că, în cazuri precum (false); …, capcana ERR este executată în sub-shell, deci nu poate determina părăsirea părintelui.

Comentarii

  • Recent, am experimentat puțin și am descoperit o modalitate convenabilă de a remedia comportamentul ||, care permite gestionarea cu ușurință a erorilor fără a utiliza capcane. Consultați răspunsul meu . Ce părere aveți despre această metodă?
  • @ sam.kozin Nu am ‘ nu aveți timp să vă examinați în detaliu răspunsul, în principiu arată bine. În afară de portabilitate, care sunt avantajele față de capcana ERR a ksh / bash / zsh ‘?
  • Probabil că singurul beneficiu este compozibilitatea, deoarece nu riscați ‘ să riscați să suprascrieți o altă capcană care a fost setată înainte de a rula funcția. Care este o caracteristică utilă atunci când id = „d72b74b94c”>

redactarea unor funcții comune pe care ulterior le veți sursa și utiliza din alte scripturi. Un alt avantaj ar putea fi compatibilitate POSIX completă, deși nu este atât de importantă, deoareceERRpseudo-semnalul este acceptat în toate shell-urile majore. Vă mulțumim pentru recenzie! =)

  • @ sam.kozin Am uitat să scriu în comentariul meu anterior: poate doriți să postați acest lucru pe Revizuirea codului și să postați un link în camera de chat .
  • Vă mulțumim pentru sugestie, voi ‘ voi încerca să urmez aceasta. ‘ nu știam despre revizuirea codului.
  • Răspuns

    Pentru a extinde răspunsul @Gilles „:

    Într-adevăr, set -e nu funcționează comenzi în interior dacă folosiți operatorul || după ele, chiar dacă le rulați într-un subshell; de exemplu, acest lucru nu ar funcționa:

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

    Dar || este necesar operatorul pentru a preveni revenirea din funcția exterioară înainte de curățare.

    Există un mic truc care poate fi folosit pentru a remedia acest lucru: rulați comanda interioară în fundal și apoi imediat asteapta.Elementul încorporat wait va returna codul de ieșire al comenzii interioare, iar acum utilizați || după wait, nu funcția interioară, deci set -e funcționează corect în interiorul acestuia din urmă:

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

    Iată funcția generică care se bazează pe această idee. Ar trebui să funcționeze în toate shell-urile compatibile POSIX dacă eliminați local cuvinte cheie, adică înlocuiți toate local x=y cu doar 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 }  

    Exemplu de utilizare:

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

    Executarea exemplului:

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

    singurul lucru de care trebuie să fii conștient atunci când folosești această metodă este că toate modificările variabilelor Shell efectuate din com dacă treceți la run nu se va răspândi către funcția de apelare, deoarece comanda rulează într-un subshell.

    Răspuns

    Nu spuneți ce anume înțelegeți prin catch — raportați și continuați; întrerupeți procesarea ulterioară?

    Deoarece cd returnează o stare diferită de zero la eșec, puteți face:

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

    Puteți ieși pur și simplu în caz de eșec:

    cd -- "$1" || exit 1 

    Sau, faceți ecou propriului mesaj și ieșiți:

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

    Și / sau suprimă eroarea furnizată de cd la eșec:

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

    După standarde, comenzile ar trebui să plaseze mesaje de eroare pe STDERR (descriptorul de fișier 2). Astfel, 2>/dev/null spune redirecționează STDERR către „bit-bucket” cunoscut de /dev/null.

    (nu uitați pentru a cita variabilele și a marca sfârșitul opțiunilor pentru cd).

    Comentarii

    • @Stephane Punctul Chazelas de a cita și semnaliza sfârșitul opțiunilor bine luat. Vă mulțumim pentru editare.

    Răspundeți

    De fapt pentru cazul dvs. aș spune că logica poate fi îmbunătățită.

    În loc de cd și apoi verificați dacă există, verificați dacă există, apoi intrați în director.

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

    Dar dacă scopul tău este de a reduce la tăcere posibilele erori, atunci cd -- "$1" 2>/dev/null, dar acest lucru te va face să depanezi în viitor mai greu. Puteți verifica dacă testarea semnalizări la: Bash dacă documentație :

    Comentarii

    • Acest răspuns nu reușește să citați variabila $1 și va eșua dacă variabila respectivă conține spații libere sau alte metacaractere de shell. De asemenea, nu verifică dacă utilizatorul are permisiunea de a cd în el.
    • De fapt, încercam să verific dacă există un anumit director, nu neapărat cd pentru el . Dar pentru că nu ‘ nu știam mai bine, m-am gândit că încercarea de a cd-l va provoca o eroare dacă nu ar exista, de ce să nu o prind? Nu ‘ nu știam despre dacă [-d $ 1] care ‘ este exact ceea ce aveam nevoie. Așadar, mulțumesc mult! (Am ‘ folosit pentru a proram Java și căutarea unui director într-o instrucțiune if nu este tocmai obișnuită în Java)

    Lasă un răspuns

    Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *