Hvordan fange en feil i et Linux bash-skript?

Jeg laget følgende skript:

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

Skriptet fungerer, men ved siden av ekko, det er også utdata når

cd $1 

mislykkes ved utførelse.

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

Er det mulig å fange dette?

Kommentarer

  • Bare en FYI, du kan også gjøre dette mye enklere; test -d /path/to/directory (eller [[ -d /path/to/directory ]] i bash) vil fortelle deg om et gitt mål er en katalog eller ikke, og det vil gjøre det stille.
  • @Patrick, som bare tester om den ‘ en katalog, ikke hvis du kan cd inn i den.
  • @StephaneChazelas ja. Funksjonsnavnet er directoryExists.
  • Se et detaljert svar her: Hev feil i et Bash-skript .

Svar

Skriptet ditt endrer kataloger når det kjører, noe som betyr at det ikke vil fungere med en serie med relative banenavn. Du kommenterte senere at du bare ville sjekke om katalogen eksisterer, ikke muligheten til å bruke cd, så svarene trenger ikke å bruke cd i det hele tatt. Revidert. Bruker tput og farger fra 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 } 

(redigert å bruke den mer usårbare printf i stedet for den problematiske echo som kan virke på rømningssekvenser i teksten.)

Kommentarer

  • Det løser også (med mindre xpg_echo er på) problemene når filnavn inneholder tilbakeslagstegn.

Svar

Bruk set -e for å stille inn exit-on-error-modus: hvis en enkel kommando returnerer en status som ikke er null (indikerer feil), skallet går ut.

Vær oppmerksom på at set -e ikke alltid sparker inn. Kommandoer i testposisjoner får mislykkes (f.eks. if failing_command, failing_command || fallback). Kommandoer i subshell fører bare til å avslutte subshell, ikke foreldre: set -e; (false); echo foo viser foo.

Alternativt, eller i tillegg, i bash (og ksh og zsh, men ikke ren sh), kan du spesifisere en kommando som utføres i tilfelle en kommando returnerer en status som ikke er null, med ERR -fellen, f.eks. trap "err=$?; echo >&2 "Exiting on error $err"; exit $err" ERR. Merk at i tilfeller som (false); …, blir ERR-fellen utført i subshell, slik at den ikke kan føre til at foreldrene slutter.

Kommentarer

  • Nylig eksperimenterte jeg litt og oppdaget en praktisk måte å fikse || oppførsel på, som gjør det enkelt å gjøre riktig feilhåndtering uten å bruke feller. Se mitt svar . Hva synes du om den metoden?
  • @ sam.kozin Jeg vet ikke ‘ t har tid til å se gjennom svaret ditt i detalj, det ser i prinsippet bra ut. Bortsett fra bærbarhet, hva er fordelene med ksh / bash / zsh ‘ s ERR trap?
  • Den eneste fordelen er sannsynligvis komposabilitet, da du ikke ‘ t risikerer å overskrive en annen felle som ble satt før du kjører. Hvilket er en nyttig funksjon når du ‘ skriver om en vanlig funksjon som du senere vil hente og bruke fra andre skript. En annen fordel kan være full POSIX-kompatibilitet, selv om det ikke er så viktig som ERR pseudosignal støttes i alle større skjell. Takk for anmeldelsen! =)
  • @ sam.kozin Jeg glemte å skrive i min forrige kommentar: det kan være lurt å legge ut dette på Code Review og legge ut en lenke i chatterom .
  • Takk for forslaget, jeg ‘ Jeg prøver å følge den. Visste ‘ ikke om Code Review.

Svar

For å utvide svaret på @Gilles :

Faktisk fungerer set -e ikke innvendige kommandoer hvis du bruker || operator etter dem, selv om du kjører dem i en subshell; for eksempel vil dette ikke fungere:

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

Men || operatør er nødvendig for å forhindre retur fra ytre funksjon før opprydding.

Det er et lite triks som kan brukes til å fikse dette: kjør den indre kommandoen i bakgrunnen, og deretter umiddelbart vent på det.wait innebygd vil returnere utgangskoden til den indre kommandoen, og nå bruker du || etter wait, ikke den indre funksjonen, så set -e fungerer ordentlig inne i sistnevnte:

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

Her er den generiske funksjonen som bygger på denne ideen. Den skal fungere i alle POSIX-kompatible skall hvis du fjerner local nøkkelord, dvs. erstatt alle local x=y med bare 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 }  

Eksempel på bruk:

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

Kjører eksemplet:

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

The det eneste du må være oppmerksom på når du bruker denne metoden, er at alle modifikasjoner av Shell-variabler gjort fra com mandat du sender til run vil ikke spre seg til anropsfunksjonen, fordi kommandoen kjører i et subshell.

Svar

Du sier ikke hva du mener med catch — rapporter og fortsett; avbryte videre behandling?

Siden cd returnerer en status som ikke er null ved feil, kan du gjøre:

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

Du kan ganske enkelt avslutte ved feil:

cd -- "$1" || exit 1 

Eller ekko din egen melding og avslutt:

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

Og / eller undertrykk feilen gitt av cd ved feil:

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

Som standard skal kommandoer plassere feilmeldinger på STDERR (filbeskrivelse 2). Dermed sier 2>/dev/null omdirigere STDERR til «bit-bøtte» kjent av /dev/null.

(ikke glem for å sitere variablene dine og markere slutten på alternativene for cd).

Kommentarer

  • @Stephane Chazelas poeng med å sitere og signalisere slutten av alternativene godt tatt. Takk for redigeringen.

Svar

Egentlig for ditt tilfelle vil jeg si at logikken kan forbedres.

I stedet for cd og deretter sjekke om den eksisterer, sjekk om den eksisterer, og gå deretter inn i katalogen.

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

Men hvis formålet ditt er å dempe de mulige feilene, så cd -- "$1" 2>/dev/null, men dette vil gjøre at du feilsøker i fremtiden vanskeligere. Du kan sjekke om testingen flagg på: Bash hvis dokumentasjon :

Kommentarer

  • Dette svaret mislykkes sitat $1 variabelen og mislykkes hvis den variabelen inneholder blanke eller andre skallmetategn. Det kan ikke sjekke om brukeren har tillatelse til å cd inn i den.
  • Jeg prøvde faktisk å sjekke om en bestemt katalog eksisterte, ikke nødvendigvis cd til den . Men fordi jeg ikke ‘ ikke visste bedre, tenkte jeg å prøve å cd til den ville forårsake en feil hvis den ikke eksisterte, så hvorfor ikke fange den? Jeg visste ikke ‘ om if [-d $ 1] som ‘ er akkurat det jeg trengte. Så tusen takk! (Jeg ‘ jeg pleide å forhåndsinnstille Java, og se etter en katalog i en if-setning er ikke akkurat vanlig i Java)

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *