What Does 'Set -O Errtrace' Do in a Shell Script

What does `set -o errtrace` do in a shell script?

From the manual:

errtrace
Same as -E.

-E If set, any trap on ERR is inherited by shell functions,
command substitutions, and commands executed in a sub‐
shell environment. The ERR trap is normally not inher‐
ited in such cases.

When errtrace is enabled, the ERR trap is also triggered when the error (a command returning a nonzero code) occurs inside a function or a subshell. Another way to put it is that the context of a function or a subshell does not inherit the ERR trap unless errtrace is enabled.

#!/bin/bash

set -o errtrace

function x {
echo "X begins."
false
echo "X ends."
}

function y {
echo "Y begins."
false
echo "Y ends."
}

trap 'echo "ERR trap called in ${FUNCNAME-main context}."' ERR
x
y
false
true

Output:

X begins.
ERR trap called in x.
X ends.
Y begins.
ERR trap called in y.
Y ends.
ERR trap called in main context.

When errtrace is not enabled:

X begins.
X ends.
Y begins.
Y ends.
ERR trap called in main context.

What does set -e mean in a bash script?

From help set :

  -e  Exit immediately if a command exits with a non-zero status.

But it's considered bad practice by some (bash FAQ and irc freenode #bash FAQ authors). It's recommended to use:

trap 'do_something' ERR

to run do_something function when errors occur.

See http://mywiki.wooledge.org/BashFAQ/105

How to trap ERR when using 'set -e' in Bash

chepner's answer is the best solution: If you want to combine set -e (same as: set -o errexit) with an ERR trap, also use set -o errtrace (same as: set -E).

In short: use set -eE in lieu of just set -e:

#!/bin/bash

set -eE # same as: `set -o errexit -o errtrace`
trap 'echo BOO!' ERR

function func(){
ls /root/
}

# Thanks to -E / -o errtrace, this still triggers the trap,
# even though the failure occurs *inside the function*.
func

A more sophisticated example trap example that prints the message in red and also prints the exit code:

trap 'printf "\e[31m%s: %s\e[m\n" "BOO!" $?' ERR


man bash says about set -o errtrace / 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.

What I believe is happening:

  • Without -e: The ls command fails inside your function, and, due to being the last command in the function, the function reports ls's nonzero exit code to the caller, your top-level script scope. In that scope, the ERR trap is in effect, and it is invoked (but note that execution will continue, unless you explicitly call exit from the trap).

  • With -e (but without -E): The ls command fails inside your function, and because set -e is in effect, Bash instantly exits, directly from the function scope - and since there is no ERR trap in effect there (because it wasn't inherited from the parent scope), your trap is not called.

While the man page is not incorrect, I agree that this behavior is not exactly obvious - you have to infer it.

Reuse bash error handling trap logic with set -e command cannot invoke error handling function correctly

@Charles Thanks for the suggestions. I will seriously consider about the pros and cons to improve my code.

For now, I found a stackoverflow link that is related to my issue. After I replaced "set -e" with "set -eE -o functrace", I get what I expect to achieve.

chepner's answer is the best solution: If you want to combine set -e (same as: set -o errexit) with an ERR trap, also use set -o errtrace (same as: set -E).

In short: use set -eE in lieu of just set -e:

#!/bin/bash

set -eE # same as: `set -o errexit -o errtrace`
trap 'echo BOO!' ERR

function func(){
ls /root/
}

# Thanks to -E / -o errtrace, this still triggers the trap,
# even though the failure occurs *inside the function*.
func

man bash says about set -o errtrace / 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.

What I believe is happening:

  • Without -e: The ls command fails inside your function, and, due to being the last command in the function, the function reports ls's nonzero exit code to the caller, your top-level script scope. In that scope, the ERR trap is in effect, and it is invoked (but note that execution will continue, unless you explicitly call exit from the trap).

  • With -e (but without -E): The ls command fails inside your function, and because set -e is in effect, Bash instantly exits, directly from the function scope - and since there is no ERR trap in effect there (because it wasn't inherited from the parent scope), your trap is not called.

While the man page is not incorrect, I agree that this behavior is not exactly obvious - you have to infer it.

How to use `set -e` inside a bash command substitution?

Q: How would you go about implementing this without set -e (i.e. print the output of a function only if something went wrong while executing it)?

You may use this way by checking return value of the function:

#!/usr/bin/env bash

foo() {
local n=$RANDOM
echo "Foo working with random=$n ..."
(($n % 2))
}

echo "Doing something that could fail..."
a="$(foo 2>&1)"
code=$?
if (($code == 0)); then
echo "Success!"
else
printf '{"ErrorCode": %d, "ErrorMessage": "%s"}\n' $code "$a"
exit $code
fi

Now run it as:

$> ./errScript.sh
Doing something that could fail...
Success!
$> ./errScript.sh
Doing something that could fail...
{"ErrorCode": 1, "ErrorMessage": "Foo working with random=27662 ..."}
$> ./errScript.sh
Doing something that could fail...
Success!
$> ./errScript.sh
Doing something that could fail...
{"ErrorCode": 1, "ErrorMessage": "Foo working with random=31864 ..."}

This dummy function code returns failure if $RANDOM is even number and success for $RANDOM being odd number.



Original answer for original question

You need to enable set -e in command substitution as well:

#!/usr/bin/env bash
set -eu
set -o pipefail

foo() {
printf "Foo working... "
echo "Failed!"
false # point of interest #1
true # point of interest #2
}

printf "Doing something that could fail... "
a="$(set -e; foo)"
code=$?
if (($code == 0)); then
echo "Success!"
else
echo "Error:"
printf "${a}"
exit $code
fi

Then use it as:

./errScript.sh; echo $?
Doing something that could fail... 1

However do note that using set -e is not ideal in shell scripts and it may fail to exit script in many scenarios.

Do check this important post on set -e

What does set -e and set -a do in bash.What are other options that i can use with set command

From the man page:

-a Mark variables and function which are modified or created for export to the environment of subsequent commands

-e Exit immediately if a pipeline (see Pipelines), which may consist of a single simple command (see Simple Commands), a list (see Lists),
or a compound command (see Compound Commands) returns 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 an if statement, part of any command executed in a &&
or || list except the command following the final && or ||, any
command in a pipeline but the last, or if the command’s return status
is being inverted with !. If a compound command other than a subshell
returns a non-zero status because a command failed while -e was being
ignored, the shell does not exit. A trap on ERR, if set, is executed
before the shell exits.

This option applies to the shell environment and each subshell
environment separately (see Command Execution Environment), and may
cause subshells to exit before executing all the commands in the
subshell.

If a compound command or shell function executes in a context where -e
is being ignored, none of the commands executed within the compound
command or function body will be affected by the -e setting, even if
-e is set and a command returns a failure status. If a compound command or shell function sets -e while executing in a context where
-e is ignored, that setting will not have any effect until the compound command or the command containing the function call
completes.

using set -o in a bash script to set command line editing

Run . ~/bin/editorSet.sh, not ~/bin/editorSet.sh, to execute the script's commands inside the interactive shell you're already running. (In bash, but not all POSIX shells, you can use source as a synonym for .).

Otherwise, it runs in a new shell which exits when the script does, so the configuration changes do not last past the end of the script's execution.



Related Topics



Leave a reply



Submit