Есть команда TRY CATCH в Bash

191

Я пишу сценарий оболочки и должен проверить, установлено ли приложение терминала. Я хочу использовать команду TRY/CATCH для этого, если нет более аккуратного способа.

  • 0
    Почему не если еще?
  • 0
    Это может помочь, если бы вы могли уточнить, какую проблему вы пытаетесь решить. Кажется, вы здесь не совсем новичок, но все же можете посетить Справочный центр и посмотреть справку о том, как задать хороший вопрос.
Показать ещё 3 комментария
Теги:
terminal
error-handling

10 ответов

329
Лучший ответ

Есть ли команда TRY CATCH в Bash?

Нет.

Bash не имеет столько предметов роскоши, сколько можно найти на многих языках программирования.

В bash нет try/catch; однако можно добиться аналогичного поведения с помощью && или ||.

Использование ||:

если command1 терпит неудачу, тогда command2 выполняется следующим образом

command1 || command2

Аналогично, использование &&, command2 будет выполняться, если command1 успешно

Ближайшее приближение try/catch выглядит следующим образом

{ # try

    command1 &&
    #save your output

} || { # catch
    # save log for exception 
}

Также bash содержит некоторые механизмы обработки ошибок, а также

set -e

Он немедленно остановит ваш script, если простую команду не удастся. Я думаю, что это должно было быть поведение по умолчанию. Поскольку такие ошибки почти всегда означают что-то неожиданное, на самом деле не "разумно" выполнять следующие команды.

А также почему не if...else. Это твой лучший друг.

  • 16
    При этом вам нужно позаботиться о том, чтобы код для #save your output не #save your output ошибкой, иначе блок «catch» все равно будет выполняться.
  • 0
    Что если command1 - это функция вместо команды?
Показать ещё 4 комментария
68

Основываясь на некоторых ответах, которые я нашел здесь, я сделал для себя небольшой вспомогательный файл для моих проектов:

trycatch.sh

#!/bin/bash

function try()
{
    [[ $- = *e* ]]; SAVED_OPT_E=$?
    set +e
}

function throw()
{
    exit $1
}

function catch()
{
    export ex_code=$?
    (( $SAVED_OPT_E )) && set +e
    return $ex_code
}

function throwErrors()
{
    set -e
}

function ignoreErrors()
{
    set +e
}

вот пример того, как он выглядит при использовании:

#!/bin/bash
export AnException=100
export AnotherException=101

# start with a try
try
(   # open a subshell !!!
    echo "do something"
    [ someErrorCondition ] && throw $AnException

    echo "do something more"
    executeCommandThatMightFail || throw $AnotherException

    throwErrors # automaticatly end the try block, if command-result is non-null
    echo "now on to something completely different"
    executeCommandThatMightFail

    echo "it a wonder we came so far"
    executeCommandThatFailsForSure || true # ignore a single failing command

    ignoreErrors # ignore failures of commands until further notice
    executeCommand1ThatFailsForSure
    local result = $(executeCommand2ThatFailsForSure)
    [ result != "expected error" ] && throw $AnException # ok, if it not an expected error, we want to bail out!
    executeCommand3ThatFailsForSure

    echo "finished"
)
# directly after closing the subshell you need to connect a group to the catch using ||
catch || {
    # now you can handle
    case $ex_code in
        $AnException)
            echo "AnException was thrown"
        ;;
        $AnotherException)
            echo "AnotherException was thrown"
        ;;
        *)
            echo "An unexpected exception was thrown"
            throw $ex_code # you can rethrow the "exception" causing the script to exit if not caught
        ;;
    esac
}
  • 1
    Не могли бы вы показать, как импортировать функции try catch в другой пример? (Я предполагаю, что они находятся в отдельных файлах)
  • 0
    @kilianc: я просто поставил его так: source inc / trycatch.sh.
Показать ещё 1 комментарий
54

Я разработал почти безупречную реализацию try и catch в bash, что позволяет писать код наподобие:

try 
    echo 'Hello'
    false
    echo 'This will not be displayed'

catch 
    echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"

Вы даже можете встраивать блоки try-catch внутри себя!

try {
    echo 'Hello'

    try {
        echo 'Nested Hello'
        false
        echo 'This will not execute'
    } catch {
        echo "Nested Caught (@ $__EXCEPTION_LINE__)"
    }

    false
    echo 'This will not execute too'

} catch {
    echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
}

Код является частью моего bash шаблона/рамки. Это также расширяет идею попытки и уловить такие вещи, как обработка ошибок с обратным трафиком и исключениями (плюс некоторые другие приятные функции).

Вот код, который отвечает только за попытку и улов:

set -o pipefail
shopt -s expand_aliases
declare -ig __oo__insideTryCatch=0

# if try-catch is nested, then set +e before so the parent handler doesn't catch us
alias try="[[ \$__oo__insideTryCatch -gt 0 ]] && set +e;
           __oo__insideTryCatch+=1; ( set -e;
           trap \"Exception.Capture \${LINENO}; \" ERR;"
alias catch=" ); Exception.Extract \$? || "

Exception.Capture() {
    local script="${BASH_SOURCE[1]#./}"

    if [[ ! -f /tmp/stored_exception_source ]]; then
        echo "$script" > /tmp/stored_exception_source
    fi
    if [[ ! -f /tmp/stored_exception_line ]]; then
        echo "$1" > /tmp/stored_exception_line
    fi
    return 0
}

Exception.Extract() {
    if [[ $__oo__insideTryCatch -gt 1 ]]
    then
        set -e
    fi

    __oo__insideTryCatch+=-1

    __EXCEPTION_CATCH__=( $(Exception.GetLastException) )

    local retVal=$1
    if [[ $retVal -gt 0 ]]
    then
        # BACKWARDS COMPATIBILE WAY:
        # export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-1)]}"
        # export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-2)]}"
        export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[-1]}"
        export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[-2]}"
        export __EXCEPTION__="${__EXCEPTION_CATCH__[@]:0:(${#__EXCEPTION_CATCH__[@]} - 2)}"
        return 1 # so that we may continue with a "catch"
    fi
}

Exception.GetLastException() {
    if [[ -f /tmp/stored_exception ]] && [[ -f /tmp/stored_exception_line ]] && [[ -f /tmp/stored_exception_source ]]
    then
        cat /tmp/stored_exception
        cat /tmp/stored_exception_line
        cat /tmp/stored_exception_source
    else
        echo -e " \n${BASH_LINENO[1]}\n${BASH_SOURCE[2]#./}"
    fi

    rm -f /tmp/stored_exception /tmp/stored_exception_line /tmp/stored_exception_source
    return 0
}

Не стесняйтесь использовать, развивать и вносить вклад - на GitHub.

  • 0
    @ erm3nda Рад это слышать! Я думаю, что я убил несколько ошибок после того, как опубликовал это, так что посмотрите на GitHub для обновлений (вам нужно включить 03_exception.sh и 04_try_catch.sh). Насколько я знаю, текущая версия в значительной степени пуленепробиваема.
  • 0
    Очень хорошо! Я собираюсь использовать в моем проекте. Я положил на работу через 5 минут, и мой Centos уже с Bash 4.2.46
Показать ещё 3 комментария
11

bash не отменяет выполняемое выполнение в случае, если sth обнаруживает состояние ошибки (если вы не установили флаг -e). Языки программирования, предлагающие try/catch, делают это, чтобы препятствовать "спасению" из-за этой особой ситуации (отсюда обычно называют "исключение" ).

В bash вместо этого только команда, о которой идет речь, выйдет с кодом выхода больше 0, что указывает на состояние ошибки. Конечно, вы можете это проверить, но поскольку автоматическое спасение ни от чего не происходит, попытка/улов не имеет смысла. Это просто отсутствует в этом контексте.

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

(
  echo "Do one thing"
  echo "Do another thing"
  if some_condition
  then
    exit 3  # <-- this is our simulated bailing out
  fi
  echo "Do yet another thing"
  echo "And do a last thing"
)   # <-- here we arrive after the simulated bailing out, and $? will be 3 (exit code)
if [ $? = 3 ]
then
  echo "Bail out detected"
fi

Вместо этого some_condition с if вы также можете просто попробовать команду, и в случае ее отказа (имеет код выхода больше 0) выйдите из системы:

(
  echo "Do one thing"
  echo "Do another thing"
  some_command || exit 3
  echo "Do yet another thing"
  echo "And do a last thing"
)
...

К сожалению, используя этот метод, вы ограничены 255 разными кодами выхода (1..255), и никакие объекты исключающего исключения не могут быть использованы.

Если вам нужна дополнительная информация для передачи вместе с вашим симулированным исключением, вы можете использовать stdout подоболочек, но это немного сложнее и, возможно, другой вопрос: -)

Используя вышеупомянутый флаг -e для оболочки, вы можете даже удалить этот явный оператор exit:

(
  set -e
  echo "Do one thing"
  echo "Do another thing"
  some_command
  echo "Do yet another thing"
  echo "And do a last thing"
)
...
10

Как все говорят, bash не имеет правильного синтаксиса try/catch, поддерживаемого языком. Вы можете запустить bash с аргументом -e или использовать set -e внутри script, чтобы прервать весь процесс bash, если какая-либо команда имеет ненулевой код выхода. (Вы также можете set +e временно разрешить команды с ошибкой.)

Итак, один метод имитации блока try/catch состоит в том, чтобы запустить подпроцесс, чтобы сделать работу с -e включенной. Затем в основном процессе проверьте код возврата подпроцесса.

Bash поддерживает строки heredoc, поэтому вам не нужно писать два отдельных файла, чтобы справиться с этим. В приведенном ниже примере TRY heredoc будет запускаться в отдельном экземпляре bash с включенным -e, поэтому подпроцесс будет сбой, если любая команда вернет ненулевой код выхода. Затем, в основном процессе, мы можем проверить код возврата для обработки блока catch.

#!/bin/bash

set +e
bash -e <<TRY
  echo hello
  cd /does/not/exist
  echo world
TRY
if [ $? -ne 0 ]; then
  echo caught exception
fi

Это не правильный языковой пакет try/catch, но он может поцарапать подобный зуд для вас.

7

Вы можете использовать trap:

try { block A } catch { block B } finally { block C }

переводит на:

(
  set -Ee
  function _catch {
    block B
    exit 0  # optional; use if you don't want to propagate (rethrow) error to outer shell
  }
  function _finally {
    block C
  }
  trap _catch ERR
  trap _finally EXIT
  block A
)
  • 0
    Вы также хотите -E флаг, я думаю, поэтому ловушка распространяется на функции
6

Есть так много подобных решений, которые, вероятно, работают. Ниже приведен простой и эффективный способ выполнения try/catch с объяснением в комментариях.

#!/bin/bash

function a() {
  # do some stuff here
}
function b() {
  # do more stuff here
}

# this subshell is a scope of try
# try
(
  # this flag will make to exit from current subshell on any error
  # inside it (all functions run inside will also break on any error)
  set -e
  a
  b
  # do more stuff here
)
# and here we catch errors
# catch
errorCode=$?
if [ $errorCode -ne 0 ]; then
  echo "We have an error"
  # We exit the all script with the same error, if you don't want to
  # exit it and continue, just delete this line.
  exit $errorCode
fi
4

И у вас есть ловушки http://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html, которые не совпадают, но другой метод, который вы можете использовать для этой цели

  • 0
    Сигналы на самом деле очень тонко связаны только с понятием исключений и try / catch, поскольку они не являются частью обычного потока управления программой. Но можно упомянуть об этом здесь.
1

Ты можешь сделать:

#!/bin/bash
if <command> ; then # TRY
    <do-whatever-you-want>
else # CATCH
    echo 'Exception'
    <do-whatever-you-want>
fi
0

Очень простая вещь, которую я использую:

try() {
    "$@" || (e=$?; echo "$@" > /dev/stderr; exit $e)
}
  • 1
    С правой стороны || находится в () , он запускается в подоболочке и завершается, не вызывая выхода из основной оболочки. Вместо этого используйте группировку { } .

Ещё вопросы

Сообщество Overcoder
Наверх
Меню