скрипт выхода из подоболочки

30

Рассмотрим этот фрагмент:

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if false; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

Обычно, когда funcвызывается, сценарий завершается, что является предполагаемым поведением. Тем не менее, если он выполняется в под-оболочке, например, в

result=`func`

это не выйдет из сценария. Это означает, что вызывающий код должен каждый раз проверять состояние выхода функции. Есть ли способ избежать этого? Это для чего set -e?

Эрнест AC
источник
1
я хочу, чтобы функция "stop" выводила сообщение в stderr и останавливала скрипт, но не останавливается, когда функция, которая вызывает stop, выполняется в под-оболочке, как в примере
Ernest AC
2
Конечно, потому что он выходит из подоболочки, а не из текущей. Просто вызовите функцию непосредственно: func.
1
я не могу вызвать его напрямую, потому что он возвращает строку, которая должна храниться в переменной
Ernest AC
1
@ErnestAC Пожалуйста, предоставьте все детали в оригинальном вопросе. Вышеуказанная функция не возвращает строку.
1
@htor Я изменил пример
Эрнест А.С.

Ответы:

10

Вы могли бы убить оригинальный shell ( kill $$) перед вызовом exit, и это, вероятно, сработает. Но:

  • это кажется мне некрасивым
  • он сломается, если у вас есть второй подоболочек, то есть использовать подоболочку внутри подоболочки.

Вместо этого вы можете использовать один из нескольких способов вернуть значение в Bash FAQ . К сожалению, большинство из них не так уж и хороши. Вы можете просто застрять, проверяя ошибки после каждого вызова функции ( -eимеет много проблем ). Либо так, либо переключитесь на Perl.

derobert
источник
5
Спасибо. Я бы предпочел перейти на Python.
Эрнест А.С.
2
Как я пишу, это 2019 год. Говорить кому-то "переключиться на Perl" - это смешно. Извините, что спорим, но не могли бы вы сказать кому-нибудь, разочарованному «С», перейти на Кобол, что эквивалентно ИМО? Как отмечает Эрнест, Python - гораздо лучший выбор. Я бы предпочел Ruby. В любом случае, ничего кроме Perl.
Грэм Николлс
38

Вы можете решить, что, например, состояние выхода 77 означает выход из любого уровня подоболочки и выполнить

set -E
trap '[ "$?" -ne 77 ] || exit 77' ERR

(
  echo here
  (
    echo there
    (
      exit 12 # not 77, exit only this subshell
    )
    echo ici
    exit 77 # exit all subshells
  )
  echo not here
)
echo not here either

set -E в комбинации с ERR ловушками это немного похоже на улучшенную версию set -eв том, что она позволяет вам определять свою собственную обработку ошибок.

В zsh ловушки ERR наследуются автоматически, поэтому вам не нужно set -E, вы также можете определять ловушки как TRAPERR()функции и изменять их $functions[TRAPERR], например,functions[TRAPERR]="echo was here; $functions[TRAPERR]"

Стефан Шазелас
источник
1
Интересное решение! Ясно , что более элегантно , чем kill $$.
3
Единственное, на что следует обратить внимание, эта ловушка не будет обрабатывать интерполированные команды, например echo "$(exit 77)"; сценарий будет продолжаться, как если бы мы написалиecho ""
Warbo
Interresting! Есть ли какая-то удача в (довольно старом) bash, у которого нет -E? может быть, мы должны прибегнуть к определению ловушки для сигнала USER и использованию уничтожения для этого сигнала? Я тоже проведу некоторые исследования ...
Оливье Дюлак,
Как узнать, когда не в ловушке суб-оболочки, чтобы вернуть 1 вместо 77?
выступление
7

В качестве альтернативы kill $$, вы также можете попробовать kill 0, это будет работать в случае вложенных подоболочек (все вызывающие и побочные процессы будут получать сигнал) ... но это все еще жестоко и безобразно.

Стефан Хименес
источник
2
Разве это не убило бы идентификатор процесса 0?
Эрнест А.С.
5
Это убьет всю группу процессов. Вы можете ударить вещи, которые вам не нужны (например, если вы начали некоторые вещи в фоновом режиме).
Дероберт
2
@ErnestAC см. Справочную страницу kill (2), pids ≤0 имеет особое значение.
Дероберт
0

Попробуй это ...

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if $1; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

echo "shell..."
func $1

echo "subshell..."
result=`func $1`

echo "shell..."
echo "result=$result"

Результаты, которые я получаю ...

# test_exitsubshell true
shell...
foo
subshell...
shell...
result=foo
# test_exitsubshell false
shell...
something went wrong

Заметки

  • Параметризован, чтобы позволить ifтесту быть trueилиfalse (см. 2 прогона)
  • Когда ifтест пройден false, мы никогда не достигнем подоболочки.
DocSalvager
источник
Это очень похоже на оригинальную идею, о которой пользователь писал и сказал, что она не работает. Я не думаю, что это работает для случая подоболочки. Ваш тест, использующий false, завершается после случая "shell" и никогда не попадает в тестовый "subshell". Я полагаю, что в этом случае произойдет сбой, так как подоболочка выйдет из вызова «exit 1», но не распространит ошибку на внешнюю оболочку.
stuckj
0

(Конкретный ответ Bash) Bash не имеет понятия об исключениях. Однако, если установить -o errexit (или эквивалентно: set -e) на крайнем внешнем уровне, команда с ошибкой приведет к тому, что подоболочка завершится с ненулевым состоянием выхода. Если это набор вложенных субоболочек без условий вокруг выполнения этих субоболочек, он будет эффективно «свернуть» весь сценарий и завершится.

Это может быть сложно, когда вы пытаетесь включить биты различного кода bash в больший скрипт. Один кусок bash может работать очень хорошо сам по себе, но при выполнении под errexit (или без errexit) ведет себя неожиданным образом.

[192.168.13.16 (f0f5e19e) ~ 22:58:22] # bash -o errexit / tmp / foo
что-то пошло не так
[192.168.13.16 (f0f5e19e) ~ 22:58:31] # bash / tmp / foo
что-то пошло не так
Но мы все равно попали сюда
[192.168.13.16 (f0f5e19e) ~ 22:58:37] # cat / tmp / foo
#! / Bin / Баш
стоп () {
    echo "$ {1}"
    выход 1
}

если ложно; тогда
    эхо "фу"
еще
    (
        остановить "что-то пошло не так"
    )
    эхо "Но мы все равно попали сюда"
фи
[192.168.13.16 (f0f5e19e) ~ 22:58:40] #
Брайан Крисман
источник
-2

Мой пример выхода в один лайнер:

COMAND || ( echo "ERROR – executing COMAND, exiting..." ; exit 77 );[ "$?" -eq 77 ] && exit
Висенти
источник
1
Похоже, что на самом деле это не тот ответ, который будет работать с командой, выполняющейся в под-оболочках, как это было запрошено OP ... Тем не менее, я не согласен с отрицательными голосами, полученными за ответ. Плохие голоса без комментариев и причин так же бесполезны, как и плохие ответы.
DVS