Hvordan finder jeg en fejl i et Linux bash-script?

Jeg lavede følgende 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" 

Scriptet fungerer, men ved siden af min ekko, der er også output, når

cd $1 

mislykkes ved udførelse.

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

Er det muligt at fange dette?

Kommentarer

  • Bare et FYI, du kan også gøre dette meget enklere; test -d /path/to/directory (eller [[ -d /path/to/directory ]] i bash) fortæller dig, om et givet mål er et bibliotek eller ikke, og det vil gøre det stille.
  • @Patrick, der bare tester, om det ‘ en mappe, ikke hvis du kan cd ind i det.
  • @StephaneChazelas ja. Funktionsnavnet er directoryExists.
  • Se et detaljeret svar her: Hæv fejl i et Bash-script .

Svar

Dit script ændrer mapper, når det kører, hvilket betyder, at det ikke vil arbejde med en række relative stienavne. Du kommenterede derefter senere, at du kun ville kontrollere, om kataloget eksisterede, ikke evnen til at bruge cd, så svarene behøver ikke at bruge cd overhovedet. Revideret. Brug af tput og farver 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 } 

(redigeret for at bruge den mere usårlige printf i stedet for den problematiske echo, der kan virke på escape-sekvenser i teksten.)

Kommentarer

  • Det løser også (medmindre xpg_echo er aktiveret) problemerne, når filnavne indeholder tilbageslagstegn.

Svar

Brug set -e til at indstille exit-on-error-tilstand: hvis en simpel kommando returnerer en status uden nul (hvilket indikerer fejl), skallen går ud.

Pas på, at set -e ikke altid sparker ind. Kommandoer i testpositioner får lov til at mislykkes (f.eks. if failing_command, failing_command || fallback). Kommandoer i subshell fører kun til at forlade subshell, ikke overordnet: set -e; (false); echo foo viser foo.

Alternativt eller derudover i bash (og ksh og zsh, men ikke almindelig sh), kan du angive en kommando, der udføres, hvis en kommando returnerer en status uden nul, med ERR -fælden, f.eks. trap "err=$?; echo >&2 "Exiting on error $err"; exit $err" ERR. Bemærk, at i tilfælde som (false); …, udføres ERR-fælden i subshell, så den ikke kan få forældrene til at afslutte.

Kommentarer

  • For nylig eksperimenterede jeg lidt og opdagede en bekvem måde at rette || op på, hvilket gør det let at udføre korrekt fejlhåndtering uden brug af fælder. Se mit svar . Hvad synes du om den metode?
  • @ sam.kozin Jeg don ‘ t har tid til at gennemgå dit svar i detaljer, det ser principielt godt ud. Hvad er fordelene i forhold til bærbarhed i forhold til ksh / bash / zsh ‘ s ERR-fælde?
  • Den eneste fordel er sandsynligvis komposibilitet, da du ikke ‘ ikke risikerer at overskrive en anden fælde, der blev indstillet, før du fungerer, kører. Hvilket er en nyttig funktion, når du ‘ skriver om en almindelig funktion, som du senere vil købe og bruge fra andre scripts. En anden fordel kan være fuld POSIX-kompatibilitet, selvom det ikke er så vigtigt, da ERR pseudosignal understøttes i alle større skaller. Tak for anmeldelsen! =)
  • @ sam.kozin Jeg glemte at skrive i min tidligere kommentar: det kan være en god idé at skrive dette på Code Review og sende et link i chatroom .
  • Tak for forslaget, jeg ‘ Jeg prøver at følge det. Vidste ‘ ikke om Code Review.

Svar

For at udvide svaret på @Gilles :

Faktisk fungerer set -e ikke indvendige kommandoer, hvis du bruger || operatoren efter dem, selvom du kører dem i en subshell; fx dette ville 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 at forhindre tilbagevenden fra den ydre funktion inden oprydning.

Der er et lille trick, der kan bruges til at rette dette: kør den indre kommando i baggrunden og derefter straks vente på det.wait indbygget returnerer udgangskoden for den indre kommando, og nu bruger du || efter wait, ikke den indre funktion, så set -e fungerer korrekt inde i sidstnævnte:

 #!/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 funktion, der bygger på denne idé. Den skal fungere i alle POSIX-kompatible skaller, hvis du fjerner local nøgleord, dvs. udskift 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 }  

Brugseksempel:

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

Kø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  

den eneste ting, du skal være opmærksom på, når du bruger denne metode, er at alle ændringer af Shell-variabler udført fra com mand, du sender til run, overføres ikke til opkaldsfunktionen, fordi kommandoen kører i en subshell.

Svar

Du siger ikke, hvad du præcist mener med catch — rapporter og fortsæt; afbryde yderligere behandling?

Da cd returnerer en status, der ikke er nul ved fejl, kan du gøre:

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

Du kan simpelthen afslutte ved fejl:

cd -- "$1" || exit 1 

Eller ekko din egen besked og afslut:

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

Og / eller undertryk fejlen leveret af cd ved fejl:

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

Som standard skal kommandoer placere fejlmeddelelser på STDERR (filbeskrivelse 2). Således siger 2>/dev/null omdirigere STDERR til “bit-bucket” kendt af /dev/null.

(glem ikke for at citere dine variabler og markere slutningen af indstillinger for cd).

Kommentarer

  • @Stephane Chazelas point for at citere og signalere slutningen af muligheder godt taget. Tak for redigering.

Svar

Faktisk for din sag vil jeg sige, at logikken kan forbedres.

I stedet for cd og derefter kontrollere, om den findes, skal du kontrollere, om den findes, så gå ind i biblioteket.

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

Men hvis dit formål er at dæmpe de mulige fejl, så cd -- "$1" 2>/dev/null, men dette får dig til at debugge i fremtiden sværere. Du kan kontrollere, om test flag ved: Bash hvis dokumentation :

Kommentarer

  • Dette svar kan ikke citerer $1 variablen og mislykkes, hvis den variabel indeholder emner eller andre skalmetategn. Det kontrolleres heller ikke, om brugeren har tilladelse til at cd ind i det.
  • Jeg prøvede faktisk at kontrollere, om der var en bestemt mappe, ikke nødvendigvis cd til den . Men fordi jeg ikke ‘ ikke vidste bedre, tænkte jeg at prøve at cd til det ville forårsage en fejl, hvis den ikke eksisterede, så hvorfor ikke fange den? Jeg vidste ikke ‘ om if [-d $ 1] at ‘ er nøjagtigt hvad jeg havde brug for. Så mange tak! (I ‘ m plejede at proramere Java og kontrollere for en mappe i en if-sætning er ikke ligefrem almindelig i Java)

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *