Чи є спосіб написати функцію 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)

Я розумію, що я створив під оболонку, і "вихід 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(), але вона працює не так однаково. Оновлено, щоб не використовувати setsidкоманду, оскільки це зажадає від нас запуску нового процесу.
FatalError

3
Працює. Будь-який рівень вкладеності функцій, будь-який потік ... Просто працює.
LiMar

Чи можна з цього зробити загальну функцію abort (), яка виходить, використовуючи код першого аргументу, наприклад abort 2, зробить це trap "exit 2" TERMраніше kill?
Ніл К. Обремський

@ NeilC.Obremski: Звичайно. Це головним чином питання про те, як повернути значення до оболонки верхнього рівня. Можливо, скористайтеся командою типу, tempfileщоб вибрати місце для зберігання коду у верхній оболонці, потім оболонка, що виходить, напишіть код у цей файл, а потім просто прочитайте в батьківському обробнику.
FatalError

2
Здається, це не спрацьовує, коли є вбудовані підоболонки. Дивіться альтернативне рішення для баша, використовуючи set -E тут
Рон Берк,

42

Ви можете використовувати set -eякий вихід, якщо команда виходить із ненульовим станом :

set -e 
func
set +e

Або візьміть повернене значення:

(func) || exit $?

3
Цікаво, що я бачу рішення. "set -e" у головному сценарії змушує будь-яку функцію, що повертає 1 (або виходить із нею), перервати виконання. На жаль, "set -e" кардинально змінює всю поведінку, і я не можу ним користуватися.
LiMar

1
Вам слід використовувати (()) для числових порівнянь. Слід використовувати всередині [] -gt, -eq тощо.
jordanm

16
або навітьres=$(func) || exit
glenn jackman

Я думаю, @glennjackman отримує печиво. це набагато чистіше, ніж у мене сміття!
brice

3
такий вид кидає виклик духу питання. якщо кожен виклик функції повинен вловити саму помилку, exit спочатку немає сенсу використовувати . функція може просто повернутися. / однак, якщо set -eвстановлено глобально, це має сенс
phil294

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"

1

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

function child() {
    local parent_pid="$1"
    local other="$2"
    ...
    if [[ $failed ]]; then
        kill -QUIT "$parent_pid"
    fi
}

1

Але чи є спосіб написати функцію, яка скасовує все виконання, незалежно від того, як воно називається?

Немає.

Мені просто потрібно отримати реальне повернене значення (повторюване функцією).

Ти можеш

res=$(func)
echo $?
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.