Bash продолжить выполнение скрипта при ошибке

Failed commands do not abort the script unless you explicitly configure that mode with set -e.

With regard to Bash’s dot command, things are tricky. If we invoke bash as /bin/sh then it bails the script if the . command does not find the file. If we invoke bash as /bin/bash then it doesn’t fail!

$ cat source.sh 
#!/bin/sh

. nonexistent
echo here
$ ./source.sh 
./source.sh: 3: .: nonexistent: not found
$ ed source.sh 
35
1s/sh/bash/
wq
37
$ ./source.sh 
./source.sh: line 3: nonexistent: No such file or directory
here

It does respond to set -e; if we have #!/bin/bash, and use set -e, then the echo is not reached. So one solution is to invoke bash this way.

If you want to keep the script maximally portable, it looks like you have to do the test.

The behavior of the dot command aborting the script is required by POSIX. Search for the «dot» keyword here. Quote:

If no readable file is found, a non-interactive shell shall abort; an interactive shell shall write a diagnostic message to standard error, but this condition shall not be considered a syntax error.

Arguably, this is the right thing to do, because dot is used for including pieces of the script. How can the script continue when a whole chunk of it has not been found?

Otherwise arguably, this is braindamaged behavior inconsistent with the treatment of other commands, and so Bash makes it consistent in its non-POSIX-conforming mode. If programmers want a command to fail, they can use set -e.

I tend to agree with Bash. The POSIX behavior is actually more broken than initially meets the eye, because this also doesn’t work the way you want:

if . nonexistent ; then
  echo loaded
fi

Even if the command is tested, it still aborts the script when it bails.

Thank GNU-deness we have alternative utilities, with source code.

One point missed in the existing answers is show how to inherit the error traps. The bash shell provides one such option for that using set

-E

If set, any trap on ERR is inherited by shell functions, command substitutions, and commands executed in a subshell environment. The ERR trap is normally not inherited in such cases.


Adam Rosenfield’s answer recommendation to use set -e is right in certain cases but it has its own potential pitfalls. See GreyCat’s BashFAQ — 105 — Why doesn’t set -e (or set -o errexit, or trap ERR) do what I expected?

According to the manual, set -e exits

if a simple commandexits with a non-zero status. The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test in a if statement, part of an && or || list except the command following the final && or ||, any command in a pipeline but the last, or if the command’s return value is being inverted via !«.

which means, set -e does not work under the following simple cases (detailed explanations can be found on the wiki)

  1. Using the arithmetic operator let or $((..)) ( bash 4.1 onwards) to increment a variable value as

    #!/usr/bin/env bash
    set -e
    i=0
    let i++                   # or ((i++)) on bash 4.1 or later
    echo "i is $i" 
    
  2. If the offending command is not part of the last command executed via && or ||. For e.g. the below trap wouldn’t fire when its expected to

    #!/usr/bin/env bash
    set -e
    test -d nosuchdir && echo no dir
    echo survived
    
  3. When used incorrectly in an if statement as, the exit code of the if statement is the exit code of the last executed command. In the example below the last executed command was echo which wouldn’t fire the trap, even though the test -d failed

    #!/usr/bin/env bash
    set -e
    f() { if test -d nosuchdir; then echo no dir; fi; }
    f 
    echo survived
    
  4. When used with command-substitution, they are ignored, unless inherit_errexit is set with bash 4.4

    #!/usr/bin/env bash
    set -e
    foo=$(expr 1-1; true)
    echo survived
    
  5. when you use commands that look like assignments but aren’t, such as export, declare, typeset or local. Here the function call to f will not exit as local has swept the error code that was set previously.

    set -e
    f() { local var=$(somecommand that fails); }        
    g() { local var; var=$(somecommand that fails); }
    
  6. When used in a pipeline, and the offending command is not part of the last command. For e.g. the below command would still go through. One options is to enable pipefail by returning the exit code of the first failed process:

    set -e
    somecommand that fails | cat -
    echo survived
    

The ideal recommendation is to not use set -e and implement an own version of error checking instead. More information on implementing custom error handling on one of my answers to Raise error in a Bash script

Обработка ошибок — очень важная часть любого языка программирования. У Bash нет лучшего варианта, чем другие языки программирования, для обработки ошибки скрипта. Но важно, чтобы скрипт Bash был безошибочным во время выполнения скрипта из терминала. Функция обработки ошибок может быть реализована для сценария Bash несколькими способами. В этой статье показаны различные методы обработки ошибок в сценарии Bash.

Пример 1. Обработка ошибок с использованием условного оператора

Создайте файл Bash со следующим сценарием, который показывает использование условного оператора для обработки ошибок. Первый оператор «if» используется для проверки общего количества аргументов командной строки и вывода сообщения об ошибке, если значение меньше 2. Затем значения делимого и делителя берутся из аргументов командной строки. Если значение делителя равно 0, генерируется ошибка, и сообщение об ошибке печатается в файле error.txt. Вторая команда «if» используется для проверки того, является ли файл error.txt пустым или нет. Сообщение об ошибке печатается, если файл error.txt не пуст.

#!/bin/bash
#Проверить значения аргументов
if [ $# -lt 2 ]; then
   echo "Отсутствует один или несколько аргументов."
   exit
fi
#Чтение значения делимого из первого аргумента командной строки
dividend=$1
#Читание значения делителя из второго аргумента командной строки
divisor=$2
#Деление делимого на делитель
result=`echo "scale=2; $dividend/$divisor"|bc 2>error.txt`
#Читать содержимое файла ошибки
content=`cat error.txt`
if [ -n "$content" ]; then
  #Распечатать сообщение об ошибке, если файл error.txt непустой
  echo "Произошла ошибка, кратная нулю."
else
  #Распечатать результат
  echo "$dividend/$divisor = $result"

Вывод:

Следующий вывод появляется после выполнения предыдущего скрипта без каких-либо аргументов:

andreyex@andreyex:-/Desktop/bash$ bash error1.bash 
One or more argument is missing. 
andreyex@andreyex:~/Desktop/bash$

Следующий вывод появляется после выполнения предыдущего скрипта с одним значением аргумента:

andreyex@andreyex:-/Desktop/bash$ bash error1.bash 75 
One or more argument is missing. 
andreyex@andreyex:~/Desktop/bash$

Следующий вывод появляется после выполнения предыдущего скрипта с двумя допустимыми значениями аргумента:

andreyex@andreyex:-/Desktop/bash$ bash error1.bash 
75 8 75/8 = 9.37 
andreyex@andreyex:-/Desktop/bash$

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

andreyex@andreyex:~/Desktop/bash$ bash error1.bash 75 0 
Divisible by zero error occurred. 
andreyex@andreyex:~/Desktop/bash$

Пример 2: Обработка ошибок с использованием кода состояния выхода

Создайте файл Bash со следующим сценарием, который показывает использование обработки ошибок Bash по коду состояния выхода. Любая команда Bash принимается в качестве входного значения, и эта команда выполняется позже. Если код состояния выхода не равен нулю, печатается сообщение об ошибке. В противном случае печатается сообщение об успешном выполнении.

#!/bin/bash

#Взять имя команды Linux
echo -n "Введите команду: "
read cmd_name
#Выполнить команду
$cmd_name
#Проверить, действительна ли команда,
if [ $? -ne 0 ]; then
   echo "$cmd_name - недопустимая команда."
else
   echo "$cmd_name является корректной командой."
fi

fi

Вывод:

Следующий вывод появляется после выполнения предыдущего скрипта с допустимой командой. Здесь «data» принимается как команда во входном значении, которая является допустимой:

andreyex@andreyex:-/Desktop/bash$ bash error2.bash

Enter a command: date 
Tue Dec 27 19:18:39 +06 2022 
date is a valid command. 

andreyex@andreyex:-/Desktop/bash$

Следующий вывод появляется после выполнения предыдущего скрипта для недопустимой команды. Здесь «cmd» воспринимается как недопустимая команда во входном значении:

andreyex@andreyex:-/Desktop/bash$ bash error2.bash

Enter a command: cmd
error2.bash: line 7: cmd: command not found cmd is a invalid command.

andreyex@andreyex: -/Desktop/bash$

Пример 3: остановить выполнение при первой ошибке

Создайте файл Bash со следующим сценарием, который показывает метод остановки выполнения при появлении первой ошибки сценария. В следующем скрипте используются две недопустимые команды. Таким образом, выдаются две ошибки. Сценарий останавливает выполнение после выполнения первой недопустимой команды с помощью команды «set -e».

#!/bin/bash
#Установите параметр для завершения скрипта при первой ошибке
set -e
echo 'Текущие дата и время: '
#Действительная команда
date
echo 'Текущий рабочий каталог: '
#Неверная команда
cwd
echo 'имя пользователя: '
#Действительная команда
whoami
echo 'Список файлов и папок: '
#Неверный список
list

Вывод:

Следующий вывод появляется после выполнения предыдущего скрипта. Сценарий останавливает выполнение после выполнения недопустимой команды «cwd»:

andreyex@andreyex:-/Desktop/bash$ bash error3.bash

Current date and time: Tue Dec 27 19:19:38 +06 2022
Current orking Directory:
error3.bash: line 9: cwd: command not found 

andreyex@andreyex:-/Desktop/bash$

Пример 4: остановить выполнение для неинициализированной переменной

Создайте файл Bash со следующим сценарием, который показывает метод остановки выполнения сценария для неинициализированной переменной. Значения имени пользователя и пароля берутся из значений аргументов командной строки. Если какое-либо из значений этих переменных не инициализировано, выводится сообщение об ошибке. Если обе переменные инициализированы, сценарий проверяет, являются ли имя пользователя и пароль действительными или недействительными.

#!/bin/bash
#Установите параметр завершения сценария для неинициализированной переменной
set -u
#Установите значение первого аргумента командной строки на имя пользователя
username=$1
#Проверьте правильность или недопустимость имени пользователя и пароля
password=$2
#Проверьте правильность или недопустимость имени пользователя и пароля
if [[ $username == 'admin' && $password == 'hidenseek' ]]; then
    echo "Действительный пользователь."
else
    echo "Неверный пользователь."
fi

Вывод:

Следующий вывод появляется, если сценарий выполняется без использования какого-либо значения аргумента командной строки. Скрипт останавливает выполнение после получения первой неинициализированной переменной:

andreyex@andreyex:~/Desktop/bash$ bash error4.bash 

error4.bash: line 7: $1: unbound variable 

andreyex@andreyex:~/Desktop/bash$

Следующий вывод появляется, если сценарий выполняется с одним значением аргумента командной строки. Скрипт останавливает выполнение после получения второй неинициализированной переменной:

andreyex@andreyex:-/Desktop/bash$ bash error4.bash admin 

error4.bash: line 9: $2: unbound variable 

andreyex@andreyex:-/Desktop/bash$

Следующий вывод появляется, если сценарий выполняется с двумя значениями аргумента командной строки — «admin» и «hide». Здесь имя пользователя действительно, но пароль недействителен. Итак, выводится сообщение «Invalid user»:

andreyex@andreyex:-/Desktop/bash$ bash error4.bash admin hide 

Invalid user. 

andreyex@andreyex:-/Desktop/bash$

Следующий вывод появляется, если сценарий выполняется с двумя значениями аргументов командной строки — «admin» и «hidenseek». Здесь имя пользователя и пароль действительны. Итак, выводится сообщение «Valid user»:

andreyex@andreyex:-/Desktop/bash$ bash error4.bash admin hidenseek 

Valid user. 

andreyex@andreyex:~/Desktop/bash$

Заключение

Различные способы обработки ошибок в скрипте Bash показаны в этой статье на нескольких примерах. Мы надеемся, что это поможет пользователям Bash реализовать функцию обработки ошибок в своих сценариях Bash.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

In this guide, we’ll cover how to use try catch command in Bash to handle possible errors in the bash script. Continue reading this article to find out more. 

Try Catch Command in BASH

Error handling in the bash script is difficult. The bash script has no specific try catch command, construct, or block to handle the exceptions and errors. Unlike other high-level languages, bash does not have a debugger to log the errors. 

However, there are other techniques to use different bash commands to handle errors. These error handling techniques aren’t mature like try catch constructs, but help catch potential bash errors. 

One more thing to remember is that the “try/catch” is a programming term used to handle errors in the code. Though most high-level languages have the support for try/catch keywords, you can still program logic to catch errors and exceptions. 

Therefore, in this guide, we’ll explore approaches you can use as a substitute in bash. Continue reading this guide to find out how to use try catch command in bash.

In this section, you’ll learn how to use try catch command in bash to handle basic errors. We’ll discuss six methods in detail. So, let’s get started.

Check the Exit Status

The first method is to check the exit status of the bash script. The syntax for the exit status looks like this:

if ! some_command; then
    echo "some_command returned an error"
fi

There’s a built-in variable called $? that tells the exit status of the last executed bash command. For instance, if you want to check the exit status of the bash script, you’ll use $? in combination with the if/else block. 

For this step, first, open the terminal by pressing “Ctrl + Alt + T”. Next, create a file using the nano or vim editor:

nano script.sh
vim script.sh

Add the following code in the bash script file:

# run some command
status=$?
if [ $status -eq 1 ]; then
    echo "General error"
elif [ $status -eq 2 ]; then
    echo "Misuse of shell builtins"
elif [ $status -eq 126 ]; then
    echo "Command invoked cannot execute"
elif [ $status -eq 128 ]; then
    echo "Invalid argument"
fi

   Now, you can replace the #run some command comment with any correct or wrong command to view the different error logs.

Use OR and AND Operator

Another method you can opt for is the usage of AND and OR operator in bash scripts. To elaborate, use the || and && between two commands from the terminal or inside the bash script. Here is the syntax:

command1 || command2
command1 && command2

The || operator executes command 2 if command 1 fails. On the other hand, the && operator runs command 2 if and only if command 1 executes successfully. 

For example, if you want to print something on the terminal only if the update has been completed successfully, then write:

sudo apt update && echo “Success”

The output looks like this:

use try catch command in bash

Exit on Errors in Bash

The next method to use the try catch command in bash is to exit the script on an error encounter. By default, the bash script continues to execute even if it throws an error. The error is sent to stderr, but the script execution does not stop.  

However, this default behavior does not allow error-handling mechanisms. To execute the exit on the error mechanism in bash, use the set command. For example, to add the exit on error code inside a bash script, the syntax looks like this:

set -e
#
# Some critical code blocks where no error is allowed
#
set +e

In this case, the set command causes bash to exit the execution immediately if any of the previous commands exit with a non-zero status. The non-zero status is only caused when there is an error. Otherwise, the status is always set to 0. 

For instance, to test the wrong command, type:

set -e
#
eho “This is a test code”
#
set +e

In addition, you can also use the set command with the pipeline. The default pipeline behavior only returns a non-zero exit status if the last command in the pipeline fails. So any error that occurs between the different commands in the pipe is not reflected outside the pipe. 

For example:

set -e
true | false | true   
echo "This is a test code" 

You’ll get this output:

set command in bash

The above script didn’t detect false between the pipe and continued the execution of the pipeline. However, if you want the pipeline to exit on failure, use the pipefail option with the set command. Specifically, type:

set -o pipefail -e
true | false | true       
echo "This will not be printed"

Now, the output will look like this:

pipefail in bash

Here’s the complete syntax for the exit on an error in the pipeline:

set -o pipefail -e
#
# Some critical code blocks where no error or pipeline error is allowed
#
set +o pipefail +e

In addition, here is the example that will successfully execute the installation process inside the bash script:

#!/bin/bash
set -e
sudo apt install firefox
echo "Command succeeded"

In case, there is some error, it will exit on that error.

Try Catch Functions in Bash

Since a bash script has no try/catch command, you can create your complex try catch functions to handle different exceptions and errors. This way, you’ll be able to use try catch command in bash. This’ll give you more control over the error-handling mechanism, and you can mimic the try catch behavior more flexibly. 

For example, type this code in the script file:

function try()
{
    [[ $- = *e* ]]; SAVED_OPT_E=$?
    set +e
}
function throw()
{
    exit $1
}
function catch()
{
    export exception_code=$?
    (( $SAVED_OPT_E )) && set +e
    return $exception_code
}

In this script, we’ve created three functions for try, catch, and exception.

The try part of the code consists of the actual code. The throw function raises the exception. In addition, we don’t want the bash to exit on the error, so we’ve used the set command inside the catch function. 

To demonstrate the above concept in detail, here’s another code that you can use as a try/catch mechanism in bash:

# Define custom exception types
export ERR_BAD=100
export ERR_WORSE=101
export ERR_CRITICAL=102
try
(
    echo "Start of the try block"
    # When a command returns a non-zero, a custom exception is raised.
    run-command || throw $ERR_BAD
    run-command2 || throw $ERR_WORSE
    run-command3 || throw $ERR_CRITICAL
    # This statement is not reached if there is any exception raised
    # inside the try block.
    echo "End of the try block"
)
catch || {
    case $exception_code in
        $ERR_BAD)
            echo "This error is bad"
        ;;
        $ERR_WORSE)
            echo "This error is worse"
        ;;
        $ERR_CRITICAL)
            echo "This error is critical"
        ;;
        *)
            echo "Unknown error: $exit_code"
            throw $exit_code # re-throw an unhandled exception
        ;;
    esac
}

The previous code covered only one exception, but this one handles custom exceptions depending on the error condition. Here’s the output of the above script:

Try Catch Command in Bash Using the -x Option

The -x flag is one of the options used with the bash command. It is commonly used to debug the bash execution. In addition, the execution shows the interpretation of each line of code on the console. 

To use this option with the bash command for debugging, type:

bash -x script.sh

The output looks like this:

use the -x option

Use Subroutine to Use Try Catch Command in Bash

Alternatively, you can also create subroutines with the set command to use try catch command in bash script. The syntax is:

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

Here’s the output of the above command in which we replaced some_command with this code:

touch newScript.sh

Output:

use try catch command in bash

This subroutine will exit if the exit code is greater than 0. Here is a more comprehensive code on using subroutines that simulates the try/catch command:

#!/bin/bash
function a() {
  # do some stuff here
}
function b() {
  # do more stuff here
}
# subshell of try
# try
(
  # This flag will make to exit from the 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
)
# subshell of catch
# 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 $errorCode
fi

Conclusion

In this article, we have covered the alternate statements, commands, and options that you can use to learn how to use try catch command in bash. Since the bash script does not support try/catch, using the subroutines, -x flag, set command, and exit status are some of the methods to apply the try/catch functionality. 

Interested in becoming an expert in bash? Check out how to pass named arguments in bash, learn if/else and use awk command.

If this article helped you, please share it

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

В оболочке bash нет никакого механизма поглощения исключений, такого как конструкции try/catch. Некоторые ошибки bash могут быть молча проигнорированы, но могут иметь последствия в дальнейшем. Перехват и обработка ошибок в bash

Проверка статуса завершения команды

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

if ! command; then
    echo "command returned an error"
fi

Другой (более компактный) способ инициировать обработку ошибок на основе статуса выхода — использовать OR:

<command_1> || <command_2>

С помощью оператора OR, <command_2> выполняется тогда и только тогда, когда <command_1> возвращает ненулевой статус выхода.

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

error_exit()
{
    echo "Error: $1"
    exit 1
}

bad-command || error_exit "Some error"

В Bash имеется встроенная переменная $?, которая сообщает вам статус выхода последней выполненной команды.

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

status=$?

case "$status" in
"1") echo "General error";;
"2") echo "Misuse of shell builtins";;
"126") echo "Command invoked cannot execute";;
"128") echo "Invalid argument";;
esac

Выход из сценария при ошибке в Bash

Когда возникает ошибка в сценарии bash, по умолчанию он выводит сообщение об ошибке в stderr, но продолжает выполнение в остальной части сценария. Даже если ввести неправильную команду, это не приведет к завершению работы сценария. Вы просто увидите ошибку «command not found».

Такое поведение оболочки по умолчанию может быть нежелательным для некоторых bash сценариев. Например, если скрипт содержит критический блок кода, в котором не допускаются ошибки, вы хотите, чтобы ваш скрипт немедленно завершал работу при возникновении любой ошибки внутри этого блока . Чтобы активировать это поведение «выход при ошибке» в bash, вы можете использовать команду set следующим образом.

set -e
# некоторый критический блок кода, где ошибка недопустима
set +e

Вызванная с опцией -e, команда set заставляет оболочку bash немедленно завершить работу, если любая последующая команда завершается с ненулевым статусом (вызванным состоянием ошибки). Опция +e возвращает оболочку в режим по умолчанию. set -e эквивалентна set -o errexit. Аналогично, set +e является сокращением команды set +o errexit.

set -e
true | false | true
echo "Это будет напечатано" # "false" внутри конвейера не обнаружено

Если необходимо, чтобы при любом сбое в работе конвейеров также завершался сценарий bash, необходимо добавить опцию -o pipefail.

set -o pipefail -e
true | false | true # "false" внутри конвейера определен правильно
echo "Это не будет напечатано"

Для «защиты» критический блока в сценарии от любого типов ошибок команд или ошибок конвейера, необходимо использовать следующую комбинацию команд set.

set -o pipefail -e
# некоторый критический блок кода, в котором не допускается ошибка или ошибка конвейера
set +o pipefail +e

Понравилась статья? Поделить с друзьями:
  • B7f8cb ошибка бмв
  • Bash перенаправление вывода ошибок
  • Bash ошибка синтаксиса около неожиданной лексемы newline
  • Bartender ошибка 2622
  • B7f8c6 bmw ошибка