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


13

Zrobił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 poza moimi echami jest też wyjście kiedy

cd $1

kończy się niepowodzeniem podczas wykonywania.

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

Czy można to złapać?


Po prostu FYI, możesz to zrobić o wiele łatwiej; 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

@Patrick, który sprawdza tylko, czy jest to katalog, a nie czy można cddo niego wejść.
Stéphane Chazelas

@StephaneChazelas tak. Nazwa funkcji to directoryExists.
Patrick

Zobacz szczegółową odpowiedź tutaj: Podnieś błąd w skrypcie Bash .
codeforester

Odpowiedzi:


8

Twój skrypt zmienia katalogi podczas działania, co oznacza, że ​​nie będzie działał z serią względnych ścieżek. Później skomentowałeś, że chcesz tylko sprawdzić istnienie katalogu, a nie możliwość korzystania z niego cd, więc odpowiedzi wcale nie muszą być używane cd. Ulepszony. Używanie tput i kolory 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
}

(Edytowane, aby użyć bardziej niewrażliwego printfzamiast problematycznego, echoktóry może działać na sekwencje specjalne w tekście).


To także naprawia (chyba że xpg_echo jest włączony) problemy, gdy nazwy plików zawierają znaki odwrotnego ukośnika.
Stéphane Chazelas

12

Służy set -edo ustawiania trybu wyjścia po błędzie: jeśli proste polecenie zwraca stan niezerowy (wskazujący błąd), powłoka wychodzi.

Pamiętaj, że set -enie zawsze kopać. Polecenia na stanowiskach testowych mogą zawieść (np if failing_command, failing_command || fallback). Polecenia w podpowłoce prowadzą tylko do wyjścia z podpowłoki, a nie nadrzędnego: set -e; (false); echo foowyświetla foo.

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


Niedawno trochę poeksperymentowałem i odkryłem wygodny sposób ustalania ||zachowania, który umożliwia łatwe wykonywanie prawidłowej obsługi błędów bez użycia pułapek. Zobacz moją odpowiedź . Co sądzisz o tej metodzie?
skozin

@ sam.kozin Nie mam czasu na szczegółowe sprawdzenie odpowiedzi, co do zasady wygląda dobrze. Oprócz przenośności, jakie są zalety w porównaniu z pułapką ERR ksh / bash / zsh?
Gilles „SO- przestań być zły”

Prawdopodobnie jedyną korzyścią jest możliwość łączenia, ponieważ nie ryzykujesz zastąpienia innej pułapki, która została ustawiona przed uruchomieniem funkcji. Jest to przydatna funkcja, gdy piszesz jakąś wspólną funkcję, którą później pozyskasz i będziesz używać z innych skryptów. Kolejną korzyścią może być pełna kompatybilność z POSIX, choć nie jest to tak ważne, ponieważ ERRpseudo-sygnał jest obsługiwany we wszystkich głównych powłokach. Dzięki za recenzję! =)
skozin 11.01.16

@ sam.kozin Zapomniałem napisać w poprzednim komentarzu: możesz opublikować to w recenzji kodu i zamieścić link na czacie .
Gilles „SO- przestań być zły”

Dzięki za sugestię, postaram się ją zastosować. Nie wiedziałem o Code Review.
skozin

6

Aby rozwinąć odpowiedź @Gilles :

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

#!/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 ||operator jest potrzebny, aby zapobiec powrotowi 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 poczekaj na to. waitWbudowane zwróci kod wyjścia polecenia wewnętrznej, a teraz używasz ||po wait, a nie funkcji wewnętrznej, więc set -edziała prawidłowo wewnątrz tego ostatniego:

#!/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 oparta na tej idei. Powinien on działać we wszystkich powłokach zgodnych z POSIX, jeśli usuniesz localsłowa kluczowe, tzn. Zastąpisz local x=yje tylko 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ć, używając tej metody, jest to, że wszystkie modyfikacje zmiennych Shell wykonane z komendy, którą przekazujesz run, nie będą propagowane do funkcji wywołującej, ponieważ komenda działa w podpowłoce.


2

Nie mówisz, co dokładnie rozumiesz przez catch--- zgłoś i kontynuuj; przerwać dalsze przetwarzanie?

Ponieważ cdw przypadku niepowodzenia zwraca stan niezerowy, możesz:

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

Możesz po prostu wyjść w przypadku awarii:

cd -- "$1" || exit 1

Lub wyświetl własną wiadomość i wyjdź:

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

I / lub ukryć błąd dostarczony przez cdw przypadku awarii:

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

Zgodnie ze standardami polecenia powinny umieszczać komunikaty o błędach w STDERR (deskryptor pliku 2). Tak 2>/dev/nullmówi przekierowanie STDERR do „wiadra bitów” znanego przez /dev/null.

(nie zapomnij podać swoich zmiennych i zaznaczyć koniec opcji dla cd).


@Stephane Chazelas dobrze zacytowany punkt cytowania i sygnalizowania końca opcji Dzięki za edycję.
JRFerguson

1

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

Zamiast cd, a następnie 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 uciszenie możliwych błędów cd -- "$1" 2>/dev/null, to jednak utrudni to debugowanie w przyszłości. Możesz sprawdzić, czy flagi testowe znajdują się w: Bash, jeśli dokumentacja :


Ta odpowiedź nie cytuje $1zmiennej i nie powiedzie się, jeśli zmienna zawiera spacje lub inne metaznaki powłoki. Nie sprawdza również, czy użytkownik ma cdna to pozwolenie .
Ian D. Allen

W rzeczywistości próbowałem sprawdzić, czy istnieje jakiś katalog, niekoniecznie cd. Ale ponieważ nie wiedziałem lepiej, pomyślałem, że próba zapisania na nim dysku CD spowoduje błąd, jeśli nie istnieje, więc dlaczego go nie złapać? Nie wiedziałem o tym, czy [-d $ 1] właśnie tego potrzebowałem. Dziękuję bardzo! (Jestem przyzwyczajony do proram Java i sprawdzanie katalogu w instrukcji if nie jest dokładnie powszechne w Javie)
Thomas De Wilde
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.