Czy istnieje sposób, aby funkcja w moim skrypcie bash automatycznie uruchamiała się przy każdym błędzie polecenia?

12

Piszę skrypt powłoki, który musi wykonać kilka poleceń, a każde polecenie zależy od każdego poprzedniego polecenia. Jeśli jakieś polecenie zawiedzie, cały skrypt powinien zawieść i wywołuję funkcję wyjścia. Mogę sprawdzić kod wyjścia każdej komendy, ale zastanawiam się, czy istnieje tryb, który mogę włączyć, lub sposób na automatyczne uruchomienie bash.

Na przykład weź następujące polecenia:

cd foo || myfunc
rm a || myfunc
cd bar || myfunc
rm b || myfunc


Czy istnieje sposób, w jaki mogę w jakiś sposób zasygnalizować powłoce przed wykonaniem tych poleceń, że powinna wywołać myfunc, jeśli którakolwiek z nich zawiedzie, aby zamiast tego napisać coś czystszego:

cd foo
rm a
cd bar
rm b
test
źródło

Odpowiedzi:

13

Możesz użyć bash trap ERR, aby skrypt zakończył działanie, jeśli dowolna komenda zwróci status większy niż zero i wykonasz swoją funkcję przy wychodzeniu.

Coś jak:

myfunc() {
  echo 'Error raised. Exiting!'
}

trap 'myfunc' ERR

# file does not exist, causing error
ls asd
echo 123

Zauważ, że pułapka bash ERR niejawnie set -o errexitlub set -enie jest POSIX.

A ERRpułapka nie jest wykonywana, gdy nie powiodła się komenda jest częścią listy poleceń bezpośrednio po untillub whilesłów kluczowych, część testu następstwie iflub elifsłów zastrzeżonych, jako część polecenia wykonywane &&lub ||lista, lub jeśli stan powrót komenda jest jest odwrócony przy użyciu !.

Cuonglm
źródło
1

(Być może) prostsza odmiana przyjętej odpowiedzi:

  1. Użyj, set -e aby spowodować niepowodzenie jednego polecenia, aby przerwać wykonywanie listy.
  2. Po prostu wypisz swoje polecenia.
  3. Użyj instrukcji if- then- else, aby wykonać polecenia obsługi błędów. Ten ostatni kawałek jest nieco trudny. Zegarek:
ustaw -e
gdyby
    cmd 1                         # np. cd foo
     cmd 2                         # np. rm a
     cmd 3                         # np. cd bar
     cmd 4                         # np. rm b
następnie
    ustaw + e
    polecenia do wykonania po sukcesie (jeśli istnieją)
jeszcze
    ustaw + e
    myfunc
    inne polecenia do wykonania po awarii (jeśli występują) 
fi

Najtrudniejsze jest to, że można umieścić swoje polecenia w tej ifczęści o if- then- else, a nie thenczęść lub elseczęści. Przypomnij sobie, że składnia ifinstrukcji jest

jeśli  lista ; następnie  lista ; [  lista elif ; następnie  lista ; ] ... [inna  lista ; ] fi 
   ↑↑↑↑
set -eMówi powłoce, że jeśli ( ) nie powiedzie się, to nie powinien iść i wykonanie ( ), i tak dalej w dół. Jeśli tak się stanie z poleceniem na zewnętrznym poziomie skryptu powłoki, powłoka zostanie zamknięta. Ponieważ jednak · · · jest listą (złożoną) po , niepowodzenie któregokolwiek z tych czterech poleceń powoduje po prostu awarię całej listy - co powoduje wykonanie klauzuli. Jeśli wszystkie cztery polecenia zakończą się powodzeniem, klauzula zostanie wykonana.cmd1cd foocmd2rm acmd1cmd2cmd3cmd4ifelsethen

W obu przypadkach pierwszą rzeczą, którą powinieneś zrobić, jest prawdopodobnie wyłączenie (wyłączenie) eopcji przez zrobienie set +e. W przeciwnym razie skrypt może zostać wydmuchany z wody, jeśli polecenie myfuncnie powiedzie się.

set -ejest określony i opisany w specyfikacji POSIX .

G-Man mówi „Przywróć Monikę”
źródło
0

Biorąc słowo „ każde polecenie zależy od każdego poprzedniego polecenia. Jeśli któreś polecenie zawiedzie, cały skrypt powinien zawieść ” dosłownie, myślę, że nie potrzebujesz żadnej specjalnej funkcji do leczenia błędów.

Wystarczy połączyć swoje polecenia z &&operatorem i ||operatorem, który robi dokładnie to, co napisałeś.

Na przykład ten łańcuch się zepsuje i wyświetli „coś poszło nie tak”, jeśli którekolwiek z poprzednich poleceń się zepsuło (bash czyta od lewej do prawej)

cd foo && rm a && cd bar && rm b || echo "something went wrong"

Prawdziwy przykład (stworzyłem dir foo, plik a, pasek dir i plik b tylko dla prawdziwej wersji demonstracyjnej):

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm a && cd bar && rm bb || echo "something is wrong"
rm: cannot remove 'bb': No such file or directory
something is wrong #mind the error in the last command

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm aa && cd bar && rm b || echo "something is wrong"
rm: cannot remove 'aa': No such file or directory
something is wrong #mind the error in second command in the row

I wreszcie, jeśli wszystkie polecenia zostały wykonane pomyślnie (kod wyjścia 0), skrypt po prostu kontynuuje:

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm a && cd bar && rm b || echo "something is wrong"
gv@debian:/home/gv/Desktop/PythonTests/foo/bar$ 
# mind that the error message is not printed since all commands were successful.

Należy pamiętać, że przy użyciu && następna komenda jest wykonywana, jeśli poprzednia komenda została zakończona kodem 0, co dla bash oznacza sukces.

Jeśli jakieś polecenie nie działa w łańcuchu, polecenie / skrypt / cokolwiek następuje || zostanie stracony.

Dla przypomnienia: jeśli musisz wykonać różne działania w zależności od złamanego polecenia, możesz to również zrobić za pomocą klasycznego skryptu, monitorując wartość, $?która zgłasza kod wyjścia dokładnie poprzedniego polecenia (zwraca zero, jeśli polecenie zostało wykonane pomyślnie lub inna liczba dodatnia, jeśli polecenie nie powiedzie się)

Przykład:

for comm in {"cd foo","rm a","cd bbar","rm b"};do  #mind the error in third command
eval $comm
    if [[ $? -ne 0 ]];then 
        echo "something is wrong in command $comm"
        break
    else 
    echo "command $comm executed succesful"
    fi
done

Wynik:

command cd foo executed succesfull
command rm a executed succesfull
bash: cd: bbar: No such file or directory
something is wrong in command cd bbar

Wskazówka: Możesz ukryć komunikat „bash: cd: bbar: Brak takiego pliku ...” poprzez zastosowanie eval $comm 2>/dev/null

George Vasiliou
źródło