Jak złapać błąd w skrypcie bash w Linuksie?

Wykonałem następujący skrypt:

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

Skrypt działa, ale oprócz mojego echo, jest również wyjście, gdy

cd $1 

kończy się niepowodzeniem podczas wykonywania.

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

Czy da się to złapać?

Komentarze

  • Do Twojej wiadomości, możesz też zrobić o wiele prościej; test -d /path/to/directory (lub [[ -d /path/to/directory ]] w bash) powie Ci, czy dany cel jest katalogiem, czy nie, i zrobi to po cichu.
  • @Patrick, który sprawdza tylko, czy znajduje się w katalogu ', a nie, jeśli możesz do niego cd.
  • @StephaneChazelas yes. Nazwa funkcji to directoryExists.
  • Zobacz szczegółową odpowiedź tutaj: Zgłoś błąd w skrypcie Bash .

Odpowiedź

Twój skrypt zmienia katalogi podczas działania, co oznacza, że nie będzie działał z seria względnych nazw ścieżek. Później skomentowałeś, że chciałeś tylko sprawdzić istnienie katalogu, a nie możliwość użycia cd, więc odpowiedzi nie muszą używać w ogóle. Ulepszony. Używając tput i kolorów z 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 } 

(Edytowano użyć bardziej niezniszczalnego printf zamiast problematycznego echo, które może działać na sekwencje specjalne w tekście.)

Komentarze

  • To również rozwiązuje (chyba że xpg_echo jest włączone) problemy, gdy nazwy plików zawierają znaki ukośnika odwrotnego.

Odpowiedź

Użyj set -e, aby ustawić tryb wyjścia po błędzie: jeśli proste polecenie zwróci niezerowy stan (wskazujący na błąd), powłoka kończy działanie.

Uważaj, set -e nie zawsze się uruchamia. Polecenia na pozycjach testowych mogą się nie powieść (np. if failing_command, failing_command || fallback). Polecenia w podpowłoce prowadzą tylko do wyjścia z podpowłoki, a nie nadrzędnej: set -e; (false); echo foo wyświetla foo.

Alternatywnie lub dodatkowo w bash (i ksh i zsh, ale nie zwykłe sh), możesz określić polecenie, które jest wykonywane w przypadku, gdy polecenie zwróci stan niezerowy, z pułapką ERR, np. trap "err=$?; echo >&2 "Exiting on error $err"; exit $err" ERR. Zauważ, że w przypadkach takich jak (false); …, pułapka ERR jest wykonywana w podpowłoce, więc nie może spowodować wyjścia rodzica.

Komentarze

  • Niedawno trochę poeksperymentowałem i odkryłem wygodny sposób naprawiania zachowania ||, który umożliwia łatwe wykonanie poprawnej obsługi błędów bez używania pułapek. Zobacz moja odpowiedź . Co myślisz o tej metodzie?
  • @ sam.kozin Nie ' nie mam czasu na szczegółowe przejrzenie odpowiedzi, z zasady wygląda dobrze. Jakie są zalety oprócz przenośności w porównaniu z pułapką ksh / bash / zsh ' s ERR?
  • Prawdopodobnie jedyną korzyścią jest możliwość tworzenia kompozycji, ponieważ nie ' nie ryzykujesz nadpisania innej pułapki, która została ustawiona przed uruchomieniem funkcji. Jest to przydatna funkcja, gdy ' ponownie piszemy jakąś typową funkcję, której później użyjesz w innych skryptach. Kolejna korzyść może być w pełni kompatybilny z POSIX, chociaż nie jest to tak ważne, ponieważ pseudosygnał ERR jest obsługiwany we wszystkich głównych powłokach. Dzięki za recenzję! =)
  • @ sam.kozin Zapomniałem napisać w moim poprzednim komentarzu: możesz go opublikować na Przegląd kodu i opublikować link w czacie .
  • Dziękuję za sugestię, ' spróbuję śledzić to. Nie ' nie wiem o przeglądzie kodu.

Odpowiedź

Aby rozwinąć odpowiedź @Gilles „:

Rzeczywiście, set -e nie działa wewnątrz poleceń, jeśli używasz operatora || po nich, nawet jeśli uruchamiasz je w podpowłoce; np. to by nie działało:

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

Ale || jest potrzebny, aby zapobiec powracaniu z funkcji zewnętrznej przed czyszczeniem.

Jest mała sztuczka, której można użyć, aby to naprawić: uruchom wewnętrzne polecenie w tle, a następnie natychmiast czekaj na to.Wbudowane wait zwróci kod zakończenia wewnętrznego polecenia, a teraz „używasz || po wait, a nie funkcja wewnętrzna, więc set -e działa poprawnie w tej ostatniej:

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

Oto ogólna funkcja, która opiera się na tym pomyśle. Powinna działać we wszystkich powłokach zgodnych z POSIX, jeśli usuniesz local słowa kluczowe, tj. zamień wszystkie local x=y na 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 }  

Przykład użycia:

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

Uruchamianie przykładu:

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

Jedyną rzeczą, o której musisz wiedzieć podczas korzystania z tej metody, jest to, że wszystkie modyfikacje zmiennych powłoki zostały wykonane z pliku com Polecenie przekazane do run nie zostanie przeniesione do funkcji wywołującej, ponieważ polecenie działa w podpowłoce.

Odpowiedz

Nie mówisz, co dokładnie masz na myśli, pisząc catch – zgłoś i kontynuuj; przerwać dalsze przetwarzanie?

Ponieważ cd zwraca stan niezerowy w przypadku niepowodzenia, możesz:

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

Możesz po prostu zakończyć w przypadku niepowodzenia:

cd -- "$1" || exit 1 

Lub wyświetl swoją wiadomość i zakończ:

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

I / lub pomiń błąd dostarczony przez cd w przypadku błędu:

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

Standardowo polecenia powinny umieszczać komunikaty o błędach w STDERR (deskryptor pliku 2). Dlatego 2>/dev/null mówi, że przekierowuje STDERR do „bit-bucket” znanego przez /dev/null.

(nie zapomnij cytowanie zmiennych i oznaczanie końca opcji dla cd).

Komentarze

  • @Stephane Chazelas cytuje i sygnalizuje koniec opcji. Dziękujemy za edycję.

Odpowiedź

Właściwie w twoim przypadku powiedziałbym, że logikę można poprawić.

Zamiast cd i sprawdź, czy istnieje, sprawdź, czy istnieje, a następnie przejdź do katalogu.

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

Ale jeśli Twoim celem jest wyciszenie możliwych błędów, cd -- "$1" 2>/dev/null, ale to utrudni debugowanie w przyszłości. Możesz sprawdzić, czy testowanie flagi w: Bash jeśli dokumentacja :

Komentarze

  • Ta odpowiedź nie zacytuje zmienną $1 i zakończy się niepowodzeniem, jeśli ta zmienna zawiera spacje lub inne metaznaki powłoki. Nie sprawdza również, czy użytkownik ma uprawnienia do cd do niego.
  • W rzeczywistości próbowałem sprawdzić, czy istnieje określony katalog, niekoniecznie cd do niego . Ale ponieważ nie ' nie wiedziałem lepiej, pomyślałem, że próba włożenia do niego dysku CD spowoduje błąd, jeśli nie istnieje, więc dlaczego go nie złapać? Nie wiem ' o tym, czy [-d $ 1] to ' jest dokładnie tym, czego potrzebuję. Więc bardzo dziękuję! (' użyłem do proramowania Java i sprawdzania katalogu w instrukcji if nie jest dokładnie powszechne w Javie)

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *