Czy istnieje sposób na napisanie funkcji bash, która przerywa całe wykonanie, bez względu na to, jak zostanie wywołana?

83

Używałem instrukcji „exit 1” w moich funkcjach bash do zakończenia całego skryptu i działało dobrze:

function func()
{
   echo "Goodbye"
   exit 1
}
echo "Function call will abort"
func
echo "This will never be printed"

Ale potem zdałem sobie sprawę, że nie działa, gdy zostanie wywołany:

res=$(func)

Rozumiem, że utworzyłem podpowłokę i „wyjście 1” przerywa tę podpowłokę, a nie główną…

Ale czy istnieje sposób na napisanie funkcji, która przerywa całą realizację, bez względu na jej wywołanie? Muszę tylko uzyskać prawdziwą wartość zwracaną (powtórzoną przez funkcję).

LiMar
źródło

Odpowiedzi:

90

Co ty mógł zrobić, to zarejestrować skorupę najwyższego poziomu dla TERMsygnału do wyjścia, a następnie wysłać TERMdo powłoki poziomie top:

#!/bin/bash
trap "exit 1" TERM
export TOP_PID=$$

function func()
{
   echo "Goodbye"
   kill -s TERM $TOP_PID
}

echo "Function call will abort"
echo $(func)
echo "This will never be printed"

Więc twoja funkcja wysyła TERMsygnał z powrotem do powłoki najwyższego poziomu, która jest przechwytywana i obsługiwana za pomocą dostarczonego polecenia, w tym przypadku,"exit 1" .

Błąd krytyczny
źródło
1
Myślałem o setsid()funkcji C , ale nie działa ona w ten sam sposób. Zaktualizowano, aby nie używać setsidpolecenia, ponieważ wymagałoby to rozpoczęcia nowego procesu.
FatalError
3
Pracuje. Dowolny poziom zagnieżdżania funkcji, dowolny przepływ ... Po prostu działa.
LiMar
Czy jest możliwe zrobienie z tego ogólnej funkcji abort (), która wychodzi za pomocą kodu pierwszego argumentu, np. abort 2Zrobiłaby to trap "exit 2" TERMprzed kill?
Neil C. Obremski
@ NeilC.Obremski: Jasne. Chodzi głównie o to, jak uzyskać wartość, która ma być propagowana z powrotem do powłoki najwyższego poziomu. Prawdopodobnie użyj polecenia takiego jak tempfilewybranie miejsca do przechowywania kodu w górnej powłoce, a następnie pozwól powłoce, która kończy pracę, zapisać kod do tego pliku, a następnie po prostu wczytać w module obsługi rodzica.
FatalError
2
Wydaje się, że nie działa, gdy występują pośredniczące podpowłoki. Zobacz alternatywne rozwiązanie do używania basha set -E tutaj
Ron Burk
42

Możesz użyć tego, set -ektóry kończy działanie, jeśli polecenie kończy działanie ze stanem niezerowym :

set -e 
func
set +e

Lub pobierz wartość zwracaną:

(func) || exit $?
brice
źródło
3
Co ciekawe, widzę rozwiązanie. "set -e" w głównym skrypcie powoduje, że każda funkcja zwracająca 1 (lub kończąca się wraz z nią) przerywa całe wykonanie. Niestety "set -e" zmienia drastycznie całe zachowanie i nie mogę go używać.
LiMar
1
Do porównań liczbowych należy używać (()). Należy użyć wewnątrz [] -gt, -eq, itp.
jordanm
16
lub nawetres=$(func) || exit
glenn jackman
Myślę, że @glennjackman dostaje ciasteczko. to dużo czystsze niż te śmieci, które mam!
brice
3
to jest sprzeczne z duchem pytania. jeśli każde wywołanie funkcji musi wychwycić sam błąd, nie ma sensu używać exit w pierwszej kolejności. funkcja może po prostu zwrócić. / jednak, jeśli set -ejest ustawiony globalnie, ma to sens
phil294,
2

Proces potomny nie może wymusić niejawnego zamknięcia procesu nadrzędnego. Musisz użyć jakiegoś mechanizmu sygnalizacyjnego. Opcje mogą zawierać specjalną wartość zwracaną lub być może wysyłanie jakiegoś sygnału kill, coś w rodzaju

function child() {
    local parent_pid="$1"
    local other="$2"
    ...
    if [[ $failed ]]; then
        kill -QUIT "$parent_pid"
    fi
}
Daenyth
źródło
2

Chyba lepiej

#!/bin/bash
set -e
trap "exit 1" ERR

myfunc() {
     set -x # OPTIONAL TO SHOW ERROR
     echo "Exit with failure"
     set +x # OPTIONAL
     exit 1
}
echo "BEFORE..."
myvar="$(myfunc)"
echo "AFTER..But not shown"
AmazingAlex
źródło
0

Ale czy istnieje sposób na napisanie funkcji, która przerywa całą realizację, bez względu na jej wywołanie?

Nie.

Muszę tylko uzyskać prawdziwą wartość zwracaną (powtórzoną przez funkcję).

Możesz

res=$(func)
echo $?
Luca
źródło