Hoe vang je een fout op in een linux bash-script?

Ik heb het volgende script gemaakt:

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

Het script werkt, maar naast mijn echos, is er ook de uitvoer wanneer

cd $1 

mislukt bij uitvoering.

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

Is het mogelijk om dit op te vangen?

Reacties

  • Gewoon ter informatie, je kunt dit ook een stuk eenvoudiger doen; test -d /path/to/directory (of [[ -d /path/to/directory ]] in bash) zal je vertellen of een bepaald doel een map is of niet, en het zal het rustig doen.
  • @Patrick, dat test alleen of het ‘ een directory is, niet of je er cd in kunt.
  • @StephaneChazelas ja. De functienaam is directoryExists.
  • Zie hier een gedetailleerd antwoord: Verhoog fout in een Bash-script .

Answer

Uw script verandert mappen terwijl het wordt uitgevoerd, wat betekent dat het niet werkt met een reeks relatieve padnamen. Later merkte je op dat je alleen wilde controleren of de directory bestond, niet de mogelijkheid om cd te gebruiken, dus antwoorden hoeven niet cd helemaal. Herzien. Met tput en kleuren van 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 } 

(bewerkt om de meer onkwetsbare printf te gebruiken in plaats van de problematische echo die zou kunnen werken op escape-reeksen in de tekst.)

Opmerkingen

  • Dat lost ook (tenzij xpg_echo aan is) de problemen op wanneer bestandsnamen backslash-tekens bevatten.

Antwoord

Gebruik set -e om de exit-on-error-modus in te stellen: als een eenvoudige opdracht een niet-nulstatus retourneert (geeft een fout aan), de shell wordt afgesloten.

Pas op dat set -e niet altijd start. Commandos in testposities mogen mislukken (bijv. if failing_command, failing_command || fallback). Commandos in de subshell leiden alleen tot het verlaten van de subshell, niet de bovenliggende shell: set -e; (false); echo foo geeft foo.

Alternatief, of aanvullend, in bash (en ksh en zsh, maar niet gewoon sh), kun je een commando specificeren dat “s uitgevoerd wordt als een commando een niet-nul status retourneert, met de ERR trap, bijv. trap "err=$?; echo >&2 "Exiting on error $err"; exit $err" ERR. Merk op dat in gevallen zoals (false); …, de ERR-trap wordt uitgevoerd in de subshell, dus het kan “niet veroorzaken dat de ouder afsluit.

Opmerkingen

  • Onlangs heb ik wat geëxperimenteerd en ontdekte ik een gemakkelijke manier om || gedrag te corrigeren, waardoor fouten gemakkelijk correct kunnen worden afgehandeld zonder valstrikken te gebruiken. Zie mijn antwoord . Wat vind je van die methode?
  • @ sam.kozin Ik don ‘ heb geen tijd om je antwoord in detail te bekijken, het ziet er in principe goed uit. Wat zijn, afgezien van draagbaarheid, de voordelen ten opzichte van ksh / bash / zsh ‘ s ERR-trap?
  • Waarschijnlijk is het enige voordeel de composability, aangezien je geen ‘ riskeert om een andere trap te overschrijven die is ingesteld voordat de functie wordt uitgevoerd. Dat is een handige functie wanneer je ‘ herschrijven van een algemene functie die u later uit andere scripts zult halen en gebruiken. Nog een voordeel is mogelijk volledige POSIX-compatibiliteit, hoewel het niet zo belangrijk is dat ERR pseudo-signaal wordt ondersteund in alle grote shells. Bedankt voor de review! =)
  • @ sam.kozin Ik vergat te schrijven in mijn vorige opmerking: misschien wil je dit posten op Code Review en een link in de chatroom .
  • Bedankt voor de suggestie, ik ‘ zal proberen te volgen het. Wist ‘ niet van Code Review.

Antwoord

Om het @Gilles “antwoord uit te breiden:

Inderdaad, set -e werkt niet inside-opdrachten als je de || -operator erachter gebruikt, zelfs als je ze in een subshell uitvoert; dit zou bijvoorbeeld niet “werken:

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

Maar || operator is nodig om terugkeer van de buitenste functie vóór het opschonen te voorkomen.

Er is een kleine truc die kan worden gebruikt om dit op te lossen: voer het binnenste commando op de achtergrond uit, en dan onmiddellijk wacht erop.De ingebouwde wait retourneert de afsluitcode van de binnenste opdracht, en nu “gebruik je || na wait, niet de innerlijke functie, dus set -e werkt correct binnen de laatste:

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

Hier is de generieke functie die op dit idee voortbouwt. Het zou moeten werken in alle POSIX-compatibele shells als je local zoekwoorden, dwz vervang alle local x=y door slechts 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 }  

Voorbeeld van gebruik:

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

Het voorbeeld uitvoeren:

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

De het enige dat u moet weten wanneer u deze methode gebruikt, is dat alle wijzigingen van Shell-variabelen worden gedaan vanaf de com mand die u doorgeeft aan run zal niet worden doorgegeven aan de aanroepende functie, omdat het commando in een subshell wordt uitgevoerd.

Answer

Je zegt niet precies wat je bedoelt met catch — rapporteren en doorgaan; verdere verwerking afbreken?

Aangezien cd een niet-nulstatus retourneert bij een fout, kunt u het volgende doen:

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

U kunt eenvoudigweg afsluiten bij een fout:

cd -- "$1" || exit 1 

Of echo uw eigen bericht en sluit af:

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

En / of onderdruk de fout die wordt gegeven door cd bij een fout:

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

Standaard zouden commandos foutmeldingen op STDERR (bestandsdescriptor 2) moeten plaatsen. Dus 2>/dev/null zegt om STDERR om te leiden naar de “bit-bucket” bekend onder /dev/null.

(vergeet niet om uw variabelen te citeren en het einde van de opties te markeren voor cd).

Opmerkingen

  • @Stephane Chazelas punt van citeren en signaleren einde-opties goed genomen. Bedankt voor het bewerken.

Antwoord

Eigenlijk voor jouw geval zou ik zeggen dat de logica kan worden verbeterd.

In plaats van cd en controleer of het bestaat, ga dan naar de directory.

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

Maar als het uw doel is om de mogelijke fouten het zwijgen op te leggen, dan cd -- "$1" 2>/dev/null, maar dit zal u in de toekomst moeilijker debuggen. U kunt de if testen controleren vlaggen op: Bash als documentatie :

Reacties

  • Dit antwoord faalt citeer de $1 variabele en zal mislukken als die variabele bevat spaties of andere shell-metatekens. Het lukt ook niet om te controleren of de gebruiker toestemming heeft om cd erin te plaatsen.
  • Ik probeerde eigenlijk te controleren of een bepaalde map bestond, niet noodzakelijk cd ernaartoe . Maar omdat ik ‘ niet beter wist, dacht ik dat het proberen om naar cd te gaan een fout zou veroorzaken als het niet bestond, dus waarom zou ik het niet opvangen? Ik wist niet ‘ van de if [-d $ 1] dat ‘ precies is wat ik nodig had. Heel erg bedankt! (I ‘ m gebruikt om Java te prorammen, en het controleren op een directory in een if-statement is niet bepaald gebruikelijk in Java)

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *