Hur får jag ett fel i ett Linux bash-skript?

Jag skapade följande manus:

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

Manuset fungerar, men bredvid min ekar, det finns också utdata när

cd $1 

misslyckas vid körning.

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

Är det möjligt att fånga det här?

Kommentarer

  • Bara ett FYI, du kan också göra det mycket enklare; test -d /path/to/directory (eller [[ -d /path/to/directory ]] i bash) kommer att berätta om ett visst mål är en katalog eller inte, och det kommer att göra det tyst.
  • @Patrick, det testar bara om det ’ en katalog, inte om du kan cd i den.
  • @StephaneChazelas ja. Funktionsnamnet är directoryExists.
  • Se ett detaljerat svar här: Höj fel i ett Bash-skript .

Svar

Ditt skript ändrar kataloger när det körs, vilket betyder att det inte fungerar med en serie relativa sökvägar. Du kommenterade sedan senare att du bara ville kontrollera om katalogen fanns, inte förmågan att använda cd, så svar behöver inte använda cd alls. Reviderad. Med tput och färger från 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 } 

(redigerad att använda det mer osårbara printf istället för det problematiska echo som kan agera på escape-sekvenser i texten.)

Kommentarer

  • Det löser också (om inte xpg_echo är på) problemen när filnamn innehåller bakåtvänd snedstreck.

Svar

Använd set -e för att ställa in exit-on-error-läge: om ett enkelt kommando returnerar en status som inte är noll (indikerar misslyckande), skalet går ut.

Var uppmärksam på att set -e inte alltid sparkar in. Kommandon i testpositioner får misslyckas (t.ex. if failing_command, failing_command || fallback). Kommandon i subshell leder bara till att subshell avslutas, inte den överordnade: set -e; (false); echo foo visar foo.

Alternativt eller dessutom i bash (och ksh och zsh, men inte vanligt sh), kan du ange ett kommando som utförs om ett kommando returnerar en status som noll, med ERR fällan, t.ex. trap "err=$?; echo >&2 "Exiting on error $err"; exit $err" ERR. Observera att i fall som (false); … exekveras ERR-fällan i subshell, så det kan inte få föräldern att lämna.

Kommentarer

  • Nyligen experimenterade jag lite och upptäckte ett bekvämt sätt att fixa || beteende, vilket gör det enkelt att göra korrekt felhantering utan att använda fällor. Se mitt svar . Vad tycker du om den metoden?
  • @ sam.kozin Jag don ’ t har tid att granska ditt svar i detalj, det ser bra ut i princip. Förutom portabilitet, vad är fördelarna med ksh / bash / zsh ’ s ERR trap?
  • Förmodligen är den enda fördelen komposibilitet, eftersom du inte ’ t riskerar att skriva över en annan fälla som var inställd innan du körs. Vilket är en användbar funktion när du id = ”d72b74b94c”>

skriver en vanlig funktion som du senare kommer att källa och använda från andra skript. En annan fördel kan vara full POSIX-kompatibilitet, men det är inte så viktigt eftersomERRpseudosignal stöds i alla större skal. Tack för recensionen! =)

  • @ sam.kozin Jag glömde att skriva i min tidigare kommentar: du kanske vill lägga upp detta på Kodgranskning och skicka ett länk i chattrummet .
  • Tack för förslaget, jag ’ Jag försöker följa den. Visste ’ inte om kodgranskning.
  • Svar

    För att utöka svaret på @Gilles :

    Faktum är att set -e inte fungerar inuti-kommandon om du använder || efter dem, även om du kör dem i en subshell; till exempel skulle detta inte fungera:

     #!/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 behövs för att förhindra återkomst från den yttre funktionen innan rengöring.

    Det finns ett litet trick som kan användas för att åtgärda detta: kör det inre kommandot i bakgrunden och sedan omedelbart vänta på det.Det inbyggda wait returnerar utgångskoden för det inre kommandot och nu använder du || efter wait, inte den inre funktionen, så set -e fungerar ordentligt inuti den senare:

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

    Här är den generiska funktionen som bygger på denna idé. Den ska fungera i alla POSIX-kompatibla skal om du tar bort local nyckelord, dvs ersätt alla local x=y med bara 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 }  

    Exempel på användning:

     #!/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ör exemplet:

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

    enda som du behöver vara medveten om när du använder den här metoden är att alla modifieringar av Shell-variabler gjorda från com mand du skickar till run sprids inte till anropsfunktionen, eftersom kommandot körs i en subshell.

    Svar

    Du säger inte vad du menar exakt med catch — rapportera och fortsätt; avbryta vidare bearbetning?

    Eftersom cd returnerar en status som inte är noll vid misslyckande kan du göra:

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

    Du kan helt enkelt avsluta vid misslyckande:

    cd -- "$1" || exit 1 

    Eller echo ditt eget meddelande och avsluta:

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

    Och / eller undertrycka felet från cd vid fel:

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

    Som standard ska kommandon placera felmeddelanden på STDERR (filbeskrivare 2). Således säger 2>/dev/null omdirigera STDERR till ”bit-bucket” känd av /dev/null.

    (glöm inte för att citera dina variabler och markera slutet på alternativen för cd).

    Kommentarer

    • @Stephane Chazelas poäng att citera och signalera slut-av-alternativ väl tagna. Tack för redigeringen.

    Svar

    Egentligen för ditt fall skulle jag säga att logiken kan förbättras.

    Istället för cd och kontrollera om den finns, kontrollera om den finns och gå in i katalogen.

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

    Men om ditt syfte är att tysta de möjliga felen så cd -- "$1" 2>/dev/null, men detta kommer att göra dig felsökning i framtiden svårare. Du kan kontrollera om testning flaggor på: Bash om dokumentation :

    Kommentarer

    • Det här svaret misslyckas med att citera variabeln $1 och misslyckas om den variabeln innehåller blanksteg eller andra skalmetatecken. Det går inte att kontrollera om användaren har behörighet att cd in i den.
    • Jag försökte faktiskt kontrollera om en viss katalog fanns, inte nödvändigtvis cd till den . Men eftersom jag inte ’ inte visste bättre, tänkte jag att försöka cd till det skulle orsaka ett fel om det inte fanns så varför inte fånga det? Jag visste inte ’ om if [-d $ 1] som ’ är exakt vad jag behövde. Så tack så mycket! (Jag ’ används för att proramera Java och söka efter en katalog i ett if-uttalande är inte exakt vanligt i Java)

    Lämna ett svar

    Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *