Tzafrir
Tzafrir

Reputation: 669

Bash functions ignore set -e

How can I run a function as a "tested command" and perform action on failure, while still aborting the function as soon as an error occur? Consider the following script

#!/bin/bash -e

function foo() {
   echo Entering foo
   false
   echo Should not reach this line
}

foo || echo I want to see this line on failure in foo
foo

The output I'm getting is

Entering foo
Should not reach this line
Entering foo

While I would like to get

Entering foo
I want to see this line on failure in foo
Entering foo

I guess what I'm looking for is a way to mark the function as untested command. According bash man page

 -e errexit
         Exit immediately if any untested command fails in non-interactive
         mode.  The exit status of a command is considered to be explicitly
         tested if the command is part of the list used to control an if,
         elif, while, or until; if the command is the left hand operand of
         an “&&” or “||” operator; or if the command is a pipeline preceded
         by the ! operator.  If a shell function is executed and its exit
         status is explicitly tested, all commands of the function are con‐
         sidered to be tested as well.

EDIT The expected output was wrong. edited it for clarity

Upvotes: 4

Views: 939

Answers (2)

Tzafrir
Tzafrir

Reputation: 669

I ended up wrapping the code to do so in a utility function below.

#!/bin/bash -e

# Runs given code aborting on first error and taking desired action on failure
# $1 code to invoke, can be expression or function name
# $2 error handling code, can be function name or expressions
function saferun {
  set +e
  (set -E ; trap 'exit 1' ERR ; eval $1) 
  [ $? -ne 0 ] && eval $2
  set -e
}

function foo() {
   echo Entering foo
   false
   echo Should not reach this line
}

saferun foo "echo I want to see this line on failure in foo"
foo

Let's break it down:

  • set +e and set -e are used to suppress failure on error, as otherwise the script will just exit on first error
  • trap is used to abort the execution on any error (instead of set -e) () is used to run the given code in subshell, so outer script will keep running after failure, and set -E is used to pass the trap into the subshell. so (set -E ; trap 'exit 1' ERR ; eval $1) run the given code / function aborting on first error while not exiting the whole script
  • $? -ne 0 check for failures and eval $2 runs the error handling code

Upvotes: 0

Kusalananda
Kusalananda

Reputation: 15613

set -e is disabled in the first call of foo since it's on the left hand side of ||.

Also, you would never see the I want to see this ... string being outputted, unless the last echo in foo somehow failed (it's that last echo in foo that determines the exit status of the function).

foo() {
    echo Entering foo
    false && echo Should not reach this line
}

foo || echo I want to see this line on failure in foo
foo

The above outputs (with or without set -x)

Entering foo
I want to see this line on failure in foo
Entering foo

Now false is the last executed statement in foo.

Upvotes: 3

Related Questions