Bash ERR signal not trapped in procedures? - bash

Consider the following code:
#!/bin/bash
trap 'echo "ERROR" && exit 2' ERR
proc(){
false
return 0
}
echo START
proc
echo END
The above shows output
START
END
but I would expect the false command to trigger the trap procedure for the ERR signal.
If I put false instead of the call to proc the signal is triggered and output
START
ERROR
is shown as expected. If I put the trap command again at the beginning of proc procedure, it is again being correctly trapped.
How is it so that trapping only works outside of procedures, unless trap command is repeated in the procedure? I could not find any documentation on that.
I got the same behavior on bash versions 3.1.0, 3.2.25, 4.1.17 .

Quoting man bash on FUNCTIONS:
the ERR trap is
not inherited unless the -o errtrace shell option has been enabled.
So, just add
set -o errtrace
to the script and it starts working.

Related

Make 'trap ERR' working inside bash functions with 'return' (or in any subshells)

I'm trying to use trap ERR in my scripts. But:
function hmmm() {
trap 'exit 10' ERR
echo 12>/SOME/NONEXISTING/FILE
# some commands that must not be done if previous has failed
echo "THAT MUST NOT BE PRINTED" >&2
return 5
}
echo ok1
a=$(hmmm) || status="$?"
echo "function returns: $status"
Prints
ok1
test2.sh: line 3: /SOME/NONEXISTING/FILE: No such file or directory
THAT MUST NOT BE PRINTED
function returns: 5
The same behavior with any combination of set -e, set -E, trap on top level etc. I always need to handle return code of function - so, as I undertand, I can't use trap ERR in my scripts at all - I willn't ever working. Am I right, or there is working method to enable trap ERR inside functions, subshells and sourced libraries and keep constructions like
a=$( ...somecode ... ) || result="$?"
working? Or, that more important, in example above make bash exit on error inside function ALWAYS, not depending on calling method.
Added:
In fact, I want to know is there any working way to BE SURE that errors are trapped inside functions, subshells and sourced code. Because my functions and libraries can be used by other peoples - so, I can't control how this functions are called, and, to be honest, even if I can - I will never use some behavior inside function, that can be accidentally and silently changed from outside.
trap isn't executed since it's a part of ||.
Change:
a=$(hmmm) || status="$?"
to:
a=$(hmmm)
status="$?"
From bash manual:
The ERR trap is not executed if the failed command is part of the
command list immediately following a while or until keyword, part of
the test in an if statement, part of a 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 value is being
inverted using !. These are the same conditions obeyed by the errexit
(-e) option.

Explicitly trigger an ERR trap, bash

Is there some way for a bash script to explicitly trigger a trap that is looking for an ERR signal (which is a special signal that can be explicitly called via kill, see https://stackoverflow.com/a/26261518/8236733)?
Have a trap file of the form
#!/bin/bash
error() {#do stuff like alert people via email}
trap 'error ${LINENO} $tablename' ERR
and a script of the form
#!/bin/bash
# trap to catch errors
source '/home/mapr/etl_scripts/clarity/lib.trap.sh'
{#try stuff} || {#catch stuff; exit 1;}
I had thought that the exit 1 would be enough to signal the trap, but this does not seem to be the case. Is there some other way to intentionally trigger the trap from within the script? Thanks.
The ERR trap is only executed when a command run by the shell fails, not when the shell itself exits with non-zero exit status. For your case, you want to use an EXIT handler that tests the exit status.
trap 'rv=$?; if [ "$rv" -ne 0 ]; then error $LINENO $tablename; fi' EXIT

Trap ERR only works once?

I'm writing a script that waits until a bunch of directories exist before starting a service. It basically consists of an infinite loop that breaks at the end, or continues if any of the needed directories aren't found. Simplified, the algorithm itself looks like
loop_while_false() {
trap continue ERR
while true; do
printf .
sleep 1
false
break
done
trap ERR
echo
}
(I'm aware I could accomplish this particular behavior with until or while !, but that's tangential to the question.)
The first time I run this, I get the expected output of a long series of dots until I hit ^c. But if I run it again, I just get one dot. If I don't hit ^c, but redefine the loop to be finite, then, in a new shell, the trap works multiple times. But why is ^c breaking the trap for the life of the shell? Even weirder (I spent extra time on this while StackExchange was upgrading hardware) if you write the function this way, it doesn't break:
loop_while_noread() {
trap continue ERR
while true; do
printf .
read -t1 -n1
break
done
trap ERR
echo
}
Unless you run loop_while_false first, and kill it with ^c. Here's an example session:
$ trap -p
trap -- 'shell_session_update' EXIT
$ loop_while_noread
...q
$ loop_while_noread
...r
$ loop_while_noread
....^C
$ loop_while_noread
..q
$ trap -p
trap -- 'shell_session_update' EXIT
trap -- 'continue' ERR
$ loop_while_false
.....^C
$ trap -p
trap -- 'shell_session_update' EXIT
trap -- 'continue' ERR
$ loop_while_false
.
$ loop_while_noread
.
It as if there's a weird relationship between sleep or false and trap. Is this expected behavior?
I'm using bash 3.2.57(1)-release on OS X El Capitan.
It's certainly a bug. You can work around it by changing the sleep command to:
sleep 1||:
I can't find any bug reports, but I did a little poking against 4.3.30(1) with gdb, and established that after the sleep 1 returns with an error (because it was interrupted), something fails in the execution of the trap ERR command, with the result that the SIG_INPROGRESS flag is never reset for ERR. That flag suppresses future execution of trap ERR, even though it is still enabled.
I didn't get into the part where "something fails in the execution"; when gdb steps over parse_and_execute (trap_command, tag, flags);, the function never returns and I end up back at the bash prompt, so I suppose that a longjmp happens at some point. (The SIG_INPROGRESS flag would be reset after parse_and_execute returns, so the fact that the function doesn't return explains why the flag is not reset.)
All this action is in trap.c inside _run_trap_internal.

ERR trap not called when setting readonly variable

Script test.sh:
set -euo pipefail
function _trap_ext
{
echo '_trap_ext'
}
function _trap_error
{
echo '_trap_error'
}
trap "_trap_ext" EXIT
trap "_trap_error" ERR
readonly foo='bar'
foo='bar'
echo 'foobar'
Output:
./test.sh: line 14: foo: readonly variable
_trap_ext
The script terminates at line 14 because of the error (-e option) but the _trap_error function is not called. Why not?
GNU bash, version 4.1.2(1)-release (x86_64-unknown-linux-gnu), 4.2.45(1)-release (i586-suse-linux-gnu)
This sounds like it might be a bug. From the man pages:
-e
Exit immediately if a pipeline (which may consist of a single simple command), a list, or a compound command (see SHELL above), exits with a non-zero status.
...
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 ENVIRONMENT above), and may cause subshells to exit before executing all the commands in the subshell.
From what the man pages say, it should execute the ERR trap. You can test that it works as expected in other situations by inserting a false before the foo='bar' statement.
It also appears that bash doesn't call the ERR trap on syntax errors either, so it may be that attempting to overwrite a readonly variable falls into a similar category of errors which skip the ERR trap. However, that explanation is pure speculation.

Trap syntax issue in bash

I intend to use trap to execute some clean up code in case of a failure. I have the following code, but it seems to be have some syntactical issues.
#!/bin/bash
set -e
function handle_error {
umount /mnt/chroot
losetup -d $LOOP_DEV1 $LOOP_DEV2
}
trap "{ echo \"$BASH_COMMAND failed with status code $?\"; handle_error; }" ERR
Does any one see an issue with the way the trap has been written. In case of an error the trap does get executed fine but it also throws another unwanted error message below.
/root/myscript.sh: line 60: } ERR with status code 0: command not found
##line 60 is that line of code that exited with a non zero status
How do I write it correctly to avoid the error message? Also what if I had to send arguments $LOOP_DEV1 and $LOOP_DEV2 from the main script to the trap and then to the handle_error function? Right now they are exported as environment variables in the main script. I did some search for trap examples but I couldn't get something similar.
EDIT
I changed the shebang from /bin/sh to /bin/bash. As /bin/sh was already symlinked to bash I did not expect unicorns nor did I see any.
That trap call is creating an interesting recursion, because $BASH_COMMAND (and $?) are being expanded when the trap command executes. However, $BASH_COMMAND at that point is the trap command itself, textually including $BASH_COMMAND (and some quotes and semicolons). Actually figuring out what the command to be executed when the trap fires is an interesting study, but it's not necessary to fix the problem, which you can do like this:
trap '{ echo "$BASH_COMMAND failed with status code $?"; handle_error; }' ERR
Note that replacing " with ' not only avoids immediate parameter expansion, it also avoids have to escape the inner "s.

Resources