Есть ли способ написать функцию bash, которая прерывает все выполнение, независимо от того, как она вызывается?

83

Я использовал оператор exit 1 в своих функциях bash для завершения всего скрипта, и он работал нормально:

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

Но потом я понял, что он не работает, когда его называют:

res=$(func)

Я понимаю, что я создал подоболочку, и "exit 1" прерывает эту подоболочку, а не основную ....

Но есть ли способ написать функцию, которая прерывает все выполнение, независимо от того, как она вызывается? Мне просто нужно получить реальное возвращаемое значение (отраженное функцией).

ЛиМар
источник

Ответы:

90

Что вы можете сделать, так это зарегистрировать оболочку верхнего уровня для выхода TERMсигнала, а затем отправить TERMв оболочку верхнего уровня:

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

Итак, ваша функция отправляет TERMсигнал обратно в оболочку верхнего уровня, который перехватывается и обрабатывается с помощью предоставленной команды, в данном случае "exit 1".

Фатальная ошибка
источник
1
Я думал о setsid()функции C , но она работает по-другому. Обновлено, чтобы setsidкоманда не использовалась , так как она потребовала бы от нас запуска нового процесса.
FatalError
3
Работает. Любой уровень вложенности функций, любой поток ... Просто работает.
LiMar,
Можно ли сделать общее прерывание () функции из этого , что выходит с использованием кода первого аргумента, например , abort 2будет делать trap "exit 2" TERMдо того kill?
Neil C. Obremski
@ NeilC.Obremski: Конечно. В основном это вопрос о том, как заставить значение распространяться обратно в оболочку верхнего уровня. Вероятно, используйте команду, например, tempfileвыберите место для хранения кода в верхней оболочке, затем заставьте оболочку, которая выходит, записать код в этот файл, а затем просто прочитать в родительском обработчике.
FatalError
2
Кажется, не работает, когда есть промежуточные подоболочки. Смотрите альтернативное решение для использования bash set -E здесь
Рон Бурк
42

Вы можете использовать set -ewhich exit, если команда завершает работу с ненулевым статусом :

set -e 
func
set +e

Или возьмите возвращаемое значение:

(func) || exit $?
брис
источник
3
Интересно вижу решение. "set -e" в основном скрипте заставляет любую функцию, возвращающую 1 (или завершающуюся с ней), прерывать все выполнение. К сожалению, "set -e" кардинально меняет поведение, и я не могу его использовать.
LiMar
1
Вы должны использовать (()) для числовых сравнений. Внутри [] должны использоваться -gt, -eq и т. Д.
jordanm
16
или дажеres=$(func) || exit
Гленн Джекман
Думаю, @glennjackman получает печенье. это намного чище, чем мусор у меня!
brice
3
такого рода противоречит духу вопроса. если каждый вызов функции должен улавливать саму ошибку, exit в первую очередь нет смысла использовать . функция могла просто вернуться. / однако, если set -eон установлен глобально, это имеет смысл
phil294
2

Дочерний процесс не может принудительно закрыть родительский процесс неявно. Вам нужно использовать какой-то сигнальный механизм. Параметры могут включать специальное возвращаемое значение или, возможно, отправку некоторого сигнала kill, например

function child() {
    local parent_pid="$1"
    local other="$2"
    ...
    if [[ $failed ]]; then
        kill -QUIT "$parent_pid"
    fi
}
Daenyth
источник
2

Я думаю, лучше

#!/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"
УдивительныйАлекс
источник
0

Но есть ли способ написать функцию, которая прерывает все выполнение, независимо от того, как она вызывается?

Нет.

Мне просто нужно получить реальное возвращаемое значение (отраженное функцией).

Вы можете

res=$(func)
echo $?
Лука
источник