Как заставить ловушку RETURN в bash сохранить код возврата?

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

Проблема состоит в том, что загрузка цепочки возвращаемого значения из более глубоких функций прерывается в ловушке, где результат должен быть проверен, чтобы показать сообщение.

#! /usr/bin/env bash

check_a_param() {
    [ "$1" = return_ok ] && return 0 || return 3
}

check_params() {
    # This trap should catch negative results from the functions
    #   performing actual checks, like check_a_param() below.
    return_trap() {
        local retval=$?
        [ $retval -ne 0 ] && echo 'Bad, bad… Dropping to manual setup.'
        return $retval
    }
    # check_params can be called from different functions, not only
    #   setup(). But the other functions don’t care about the return value
    #   of check_params().
    [ "${FUNCNAME[1]}" = setup ] \
        && trap "return_trap; got_retval=$?; trap - RETURN; return $got_retval;" RETURN
    check_a_param 'return_bad' || return $?
    # …
    # Here we check another parameters in the same way.
    # …
    echo 'Provided parameters are valid.'
    return 0  # To be sure.
}

ask_for_params() {
    echo 'User sets params manually step by step.'
}

setup() {
    [ "$1" = force_manual ] && local MANUAL=t
    # If gathered parameters do not pass check_params()
    #   the script shall resort to asking user for entering them.
    [ ! -v MANUAL ] && {
        check_params \
            && echo "check_params() returned with 0. Not running manual setup."
            || false
    }|| ask_for_params
    # do_the_job
}

setup "$@"  # Either empty or ‘force_manual’.

Как это должно работать:

              ↗ 3 → 3→ trap →3                     ↗ || ask_for_params ↘
 check_a_param >>> check_params >>> [ ! -v MANUAL ]                     ↓
              ↘ 0 → 0→ trap →0                     ↘ && ____________ do_the_job

Идея в том, что если проверка не пройдена, ее код возврата принудительно check_params() чтобы вернуть тоже, что, в свою очередь, вызовет || ask_for_params состояние в setup(), Но ловушка возвращает 0:

              ↗ 3 → 3→ trap →0
 check_a_param >>> check_params >>> [ ! -v MANUAL ] &&… >>> do_the_job
              ↘ 0 → 0→ trap →0

Если вы попытаетесь запустить скрипт как есть, вы должны увидеть

Bad, bad… Dropping to manual setup.
check_params() returned with 0. Not running manual setup.

Это означает, что плохой результат вызвал ловушку (!), Но функция-мать, которая его установила, не передала результат.

В попытке установить взломать я пытался

  • установить retval как глобальную переменную declare -g retval=$? в return_trap() и используйте его значение в строке, устанавливающей ловушку. Переменная установлена ​​([ -v retval ] возвращается успешно), но... не имеет значения. Смешной.
  • ладно давайтеretval=Eeh к check_params(), вне return_trap() и просто установите его $? как обычный парам. Нет, retval в функции не устанавливается значение для глобальной переменной, оно остается "Eeh". Нет, нет local директивы. По умолчанию он должен рассматриваться как глобальный. Если вы положите test=1 в check_params() а также test=3 в check_a_param() а затем распечатать его с echo $testв конце setup(), вы должны увидеть 3. По крайней мере, я делаю. declare -g здесь нет никакой разницы, как ожидалось.
  • может быть, это сфера функции? Нет, это тоже не так. перемещение return_trap() вместе с declare -g retval=Eeh не имеет никакого значения.
  • когда современное программное обеспечение означает падение, пришло время прибегнуть к старой доброй записи в файл. Давайте напечатаем ретваль в /tmp/t с помощью retval=$?; echo $retval >/tmp/t в return_trap() и прочитать его обратно с

    trap "return_trap; trap - RETURN; return $(</tmp/t)" RETURN

Теперь мы можем наконец увидеть, что последняя директива return, которая читает число из файла, на самом деле возвращает 3. Но check_params() по-прежнему возвращает 0!

++ trap - RETURN
++ return 3
+ retval2=0
+ echo 'check_params() returned with 0. Not running manual setup.'
check_params() returned with 0. Not running manual setup.

Если аргумент к trap Команда строго является именем функции, она возвращает исходный результат. Оригинальный, а не то, что return_trap() возвращается. Я попытался увеличить результат и все еще получил 3. Вы также можете спросить: "Зачем вам нужно так много раз сбрасывать ловушку?". Это чтобы избежать другой ошибки, которая заставляет ловушку срабатывать каждый раз, даже когда check_params() вызывается из другой функции. Ловушки в RETURN являются локальными вещами, они не наследуются другими функциями, если только для них явно не установлены флаги отладки или трассировки, но похоже, что они держат ловушки, установленные на них между запусками. Или Баш держит ловушки для них. Эта ловушка должна быть установлена ​​только тогда, когда check_params() вызывается из определенной функции, но если ловушка не сброшена, она продолжает срабатывать каждый раз check_a_param() возвращает значение больше нуля независимо от того, что в FUNCNAME[1],

Здесь я сдаюсь, потому что единственный выход, который я вижу сейчас, это реализовать проверку вызывающей функции перед каждым || return $? в check_params(), Но это так уродливо, что ранит мои глаза.

Я могу только добавить, что $? в установке строки ловушка всегда будет возвращать 0. Так что, если вы, например, объявите local переменная retval в return_trap(), и поставить такой код, чтобы проверить это

trap "return_trap; [ -v retval ]; echo $?; trap - RETURN; return $retval" RETURN

он будет печатать 0 независимо от того, retval на самом деле установлен или нет, но если вы используете

trap "return_trap; [ -v retval ] && echo set || echo unset; trap - RETURN; return $retval" RETURN

Будет напечатано "unset".


GNU bash, версия 4.3.39(1)-релиз (x86_64-pc-linux-gnu)

1 ответ

Решение

Достаточно смешно,

trap "return_trap; trap - RETURN" RETURN

просто работает.

[ ! -v MANUAL ] && {
    check_params; retval2=$?
    [ $retval2 -eq 0 ] \
        && echo "check_params() returned with 0. Not running manual setup." \
        || false
}|| ask_for_params

А вот и след.

+ check_a_parameter return_bad
+ '[' return_bad = return_ok ']'
+ return 3
+ return 3
++ return_trap
++ local retval=3
++ echo 3
++ '[' 3 -ne 0 ']'
++ echo 'Bad, bad… Dropping to manual setup.'
Bad, bad… Dropping to manual setup.
++ return 3
++ trap - RETURN
+ retval2=3
+ '[' 3 -eq 0 ']'
+ false
+ ask_for_params
+ echo 'User sets params manually step by step.'
User sets params manually step by step.

Поэтому ответ прост: не пытайтесь перезаписать результат в строке, переданной trap команда. Баш обрабатывает все для вас.

Другие вопросы по тегам