Меню

Bash завершить скрипт ошибкой

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

Why would you exit from a Bash script?

Let’s say you are writing a script…there’s one thing that is almost certain…

At some point, your script is likely to break because of unexpected conditions you might have not considered.

So, what can you do about it?

How can you exit a Bash script in case of errors?

Bash provides a command to exit a script if errors occur, the exit command. The argument N (exit status) can be passed to the exit command to indicate if a script is executed successfully (N = 0) or unsuccessfully (N != 0). If N is omitted the exit command takes the exit status of the last command executed.

In this guide you will learn how to use the exit command in your scripts to make them more robust and to provide a great experience to those who are using your scripts.

Let’s get started.

Exit a Bash Script With Error

What can you do to write robust scripts that don’t break in unexpected ways in case of errors?

The answer to this question is: don’t forget error handling.

Instead of “hoping” that nothing will go wrong with your program, you can predict possible failures and decide how your program will react to those.

How will you handle a failure if it occurs? What will the user see?

We will have a look at how error handling works in Bash scripting (or Shell scripting in general).

These concepts apply to all programming languages even if the way error handling is implemented varies between programming languages.

To handle errors in Bash we will use the exit command, whose syntax is:

exit N

Where N is the Bash exit code (or exit status) used to exit the script during its execution.

Different values of N are used to indicate if the script exits with success or failure.

But, why different exit codes depending on the success or failure of a script?

Because often other programs will be calling your script and they will need to be able to understand if everything goes well with the execution of your script or not as part of their error handling.

Let’s have a look at what exit 1 means.

What Are Exit 0 and Exit 1 in a Bash Script?

How do you exit a Bash script on error?

The standard convention is the following:

Bash exit code Meaning
Zero (0) Success
Non-zero (1, 2, 3, etc…) Failure

As you can see from the table above, failures can be represented with any non-zero exit codes.

For instance:

  • 1 may be used if incorrect arguments are passed to the script
  • 2 if the script cannot find a file it needs
  • 3 if the file it needs has an incorrect format
  • And so on…

You can be creative and use non-zero Bash exit codes to be very clear about the reason why your script has failed.

So, going back to the exit command.

What happens when it gets executed?

The exit command returns an exit code back to the shell.

Here’s an example…

Let’s write a script called exit_status.sh:

#!/bin/bash

echo "Exit command test"
exit 0 

So, here we are passing the exit code 0 to the exit command.

How can we verify that this is actually the code passed by the script back to the shell?

We use the $? variable. Remember it, this is very important!

$? is a variable that contains the exit code of the last command executed.

And how can we read its value?

Using the echo command, in the same way we print the value of any variable:

(localhost)$ ./exit_status.sh 
Exit command test
(localhost)$ echo $?
0

Voilà, here is the 0 exit code.

And what happens if we remove “exit 0” from the script?

#!/bin/bash

echo "Exit command test"

Run the script again and check the value of $?:

(localhost)$ ./exit_status.sh 
Exit command test
(localhost)$ echo $?
0

We still get the 0 exit code back…

So, nothing changed…why?

Because every command we execute, including the echo command (the only command in our script) returns an exit code.

And in this case the echo command executed as part of the script is returning a 0 exit code because the echo “Exit command test” command is executed successfully.

More About The Status of the Bash Exit Command

I will show you an example of how the exit code is applied to the execution of any commands.

This is really important in your Bash knowledge and I want to make sure it’s clear to you.

Here is an example with the cat command (this applies to all commands)…

I open a shell on my computer and in the current directory I have the following files:

(localhost)$ ls -al
total 16
drwxr-xr-x   4 myuser  mygroup  128  4 Jul 12:38 .
drwxr-xr-x  11 myuser  mygroup  352  4 Jul 12:38 ..
-rw-r--r--   1 myuser  mygroup   10  4 Jul 12:38 test_file1
-rw-r--r--   1 myuser  mygroup   10  4 Jul 12:38 test_file2

If I use the cat command to see the content of the file test_file1 the exit code stored in the variable $? after the execution of the cat command is 0 because the execution of the cat command is successful:

(localhost)$ cat test_file1 
Test file
(localhost)$ echo $?
0

If by mistake I try to print the content of the file test_file3 (that doesn’t exist), the value of the variable $? is not 0.

In this case it’s 1, but it can have other values different than zero. This is useful to represent multiple types of errors for a given command:

(localhost)$ cat test_file3
cat: test_file3: No such file or directory
(localhost)$ echo $?
1

All clear?

If you have any questions please let me know in the comments below.

An Example of Script Failure

Now let’s modify the echo command in the simple script we have used before.

We want run it with an incorrect syntax and see if it exits with a non-zero exit code.

Remove the double quotes at the end of the echo command as shown below:

#!/bin/bash

echo "Exit command test

If we execute the script we see the message “syntax error: unexpected end of file“:

(localhost)$ ./exit_status.sh 
./exit_status.sh: line 3: unexpected EOF while looking for matching `"'
./exit_status.sh: line 4: syntax error: unexpected end of file
(localhost)$ echo $?
2

And the exit code is 2. So a non-zero exit code as we expected.

As mentioned before, remember the $? variable because it makes a big difference in the way you handle errors and you make your scripts robust.

The Bash if else statement can also be used to write more complex logic that checks the value of the $? variable and takes different actions based on that.

Bash If Else Applied to $? Variable

Let’s see how you can use a Bash if else statement together with the $? variable.

The following script tries to create a subdirectory tmp/project in the current directory.

In the condition of the if statement we verify if the value of the variable $? is different than 0. If that’s the case we print an error message and exit the script with exit code 1.

The else branch prints a successful message and it’s executed only if the value of $? is 0.

#!/bin/bash
  
mkdir tmp/project

if [[ $? -ne 0 ]] ; then
    echo "Unable to create directory tmp/project"
    exit 1
else
    echo "Directory tmp/project created successfully"
fi

Let’s run the script:

(localhost)$ ./exit.sh 
mkdir: tmp: No such file or directory
Unable to create directory tmp/project
(localhost)$ echo $?
1

The mkdir command fails because the tmp directory doesn’t exist and as expected the exit status of the script is 1.

I want to see what happens if I update the mkdir command in the script to include the -p flag that creates the tmp directory if it doesn’t exist:

mkdir -p tmp/project

And here is the output of the script:

(localhost)$ ./exit.sh 
Directory tmp/project created successfully
(localhost)$ echo $?
0

This time the directory gets created successfully and the exit code returned by the script is zero as expected (the else branch of the if else statement gets executed).

Conclusion

We went through quite a lot!

Now you know:

  • Why exit 0 and exit 1 are used in Bash scripts.
  • How you can use the variable $? to read the exit code returned by a command (or script)
  • The way to use a Bash if else statement together with the $? variable.

And now it’s your turn…

What kind of failures do you want to handle in your script?

Let me know in the comments! 😀


Related FREE Course: Decipher Bash Scripting

Related posts:

I’m a Tech Lead, Software Engineer and Programming Coach. I want to help you in your journey to become a Super Developer!

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

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

attempt=0
until gksu command; do
  attempt=$((attempt + 1))
  if [ "$attempt" -gt 5 ]; then
    exit 1
  fi
done

exit exits the script unless it’s called in a subshell. If that part of the script is in a subshell, for instance because it’s within (...) or $(...) or part of a pipe-line, then it will only exit that subshell.

In that case, if you want the script to exit in addition to the subshell, then you’ll need to call exit upon that subshell exiting.

For instance, here with 2 nested levels of subshells:

(
  life=hard
  output=$(
    echo blah
    [ "$life" = easy ] || exit 1 # exit subshell
    echo blih not run
  ) || exit # if the subshell exits with a non-zero exit status,
            # exit as well with the same exit status

  echo not run either
) || exit # if the subshell exits with a non-zero exit status,
          # exit as well with the same exit status

It can become trickier if the subshell is part of a pipeline. bash has a special $PIPESTATUS array, similar to zsh‘s $pipestatus one that can help you here:

{
   echo foo
   exit 1
   echo bar
} | wc -c
subshell_ret=${PIPESTATUS[0]}
if [ "$subshell_ret" -ne 0 ]; then
  exit "$subshell_ret"
fi

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

А вот еще интересные материалы:

  • Яшка сломя голову остановился исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного исправьте ошибки
  • Ясность цели позволяет целеустремленно добиваться намеченного где ошибка
  • Bandicam ошибка захвата звука windows 10
  • Bandeq cyw 100n ошибки