Below is the simplified scheme of the script I am writing. The program must take parameters in different ways, so there is a fine division to several functions.
The problem is that the chainloading of the return value from deeper functions breaks on the trap, where the result is to be checked to show a message.
#! /usr/bin/env bash
check_a_param() {
[ "$1" = return_ok ] && return 0 || return 3
}
check_params() {
# This trap should catch negative results from the functions
# performing actual checks, like check_a_param() below.
return_trap() {
local retval=$?
[ $retval -ne 0 ] && echo 'Bad, bad… Dropping to manual setup.'
return $retval
}
# check_params can be called from different functions, not only
# setup(). But the other functions don’t care about the return value
# of check_params().
[ "${FUNCNAME[1]}" = setup ] \
&& trap "return_trap; got_retval=$?; trap - RETURN; return $got_retval;" RETURN
check_a_param 'return_bad' || return $?
# …
# Here we check another parameters in the same way.
# …
echo 'Provided parameters are valid.'
return 0 # To be sure.
}
ask_for_params() {
echo 'User sets params manually step by step.'
}
setup() {
[ "$1" = force_manual ] && local MANUAL=t
# If gathered parameters do not pass check_params()
# the script shall resort to asking user for entering them.
[ ! -v MANUAL ] && {
check_params \
&& echo "check_params() returned with 0. Not running manual setup."
|| false
}|| ask_for_params
# do_the_job
}
setup "$@" # Either empty or ‘force_manual’.
How it should work:
↗ 3 → 3→ trap →3 ↗ || ask_for_params ↘
check_a_param >>> check_params >>> [ ! -v MANUAL ] ↓
↘ 0 → 0→ trap →0 ↘ && ____________ do_the_job
The idea is, if a check fails, its return code forces check_params()
to return, too, which, in its turn would trigger the || ask_for_params
condition in setup()
. But the trap returns 0:
↗ 3 → 3→ trap →0
check_a_param >>> check_params >>> [ ! -v MANUAL ] &&… >>> do_the_job
↘ 0 → 0→ trap →0
If you try to run the script as is, you should see
Bad, bad… Dropping to manual setup.
check_params() returned with 0. Not running manual setup.
Which means that the bad result triggered the trap(!) but the mother function that has set it, didn’t pass the result.
In attempt to set a hack I’ve tried
- to set retval as a global variable
declare -g retval=$?
in thereturn_trap()
and use its value in the line setting the trap. The variable is set ([ -v retval ]
returns successfully), but …has no value. Funny. - okay, let’s put
retval=Eeh
to thecheck_params()
, outside thereturn_trap()
and just set it to$?
as a usual param. Nope, theretval
in the function doesn’t set the value for the global variable, it stays ‘Eeh’. No, there’s nolocal
directive. It should be treated as global by default. If you puttest=1
tocheck_params()
andtest=3
incheck_a_param()
and then print it withecho $test
at the end ofsetup()
, you should see 3. At least I do.declare -g
doesn’t make any difference here, as expected. - maybe that’s the scope of the function? No, that’s not it either. Moving
return_trap()
along withdeclare -g retval=Eeh
doesn’t make any difference. when the modern software means fall, it’s time to resort to good old writing to a file. Let’s print the retval to /tmp/t with
retval=$?; echo $retval >/tmp/t
inreturn_trap()
and read it back withtrap "return_trap; trap - RETURN; return $(</tmp/t)" RETURN
Now we can finally see that the last return directive which reads the number from the file, actually returns 3. But check_params()
still returns 0!
++ trap - RETURN
++ return 3
+ retval2=0
+ echo 'check_params() returned with 0. Not running manual setup.'
check_params() returned with 0. Not running manual setup.
If the argument to the trap
command is strictly a function name, it returns the original result. The original one, not what return_trap()
returns. I’ve tried to increment the result and still got 3.
You may also ask ‘Why would you need to unset the trap so much?’. It’s to avoid another bug, which causes the trap to trigger every time, even when check_params()
is called from another function. Traps on RETURN are local things, they aren’t inherited by another functions unless there’s debug or trace flags explicitly set on them, but it looks like they keep traps set on them between runs. Or bash keeps traps for them. This trap should only be set when check_params() is called from a specific function, but if the trap is not unset, it continues to get triggered every time check_a_param()
returns a value greater than zero independently of what’s in FUNCNAME[1]
.
Here I give up, because the only exit I see now is to implement a check on the calling function before each || return $?
in check_params()
. But it’s so ugly it hurts my eyes.
I may only add that, $?
in the line setting the trap will always return 0. So, if you, for example, declare a local
variable retval
in return_trap()
, and put such code to check it
trap "return_trap; [ -v retval ]; echo $?; trap - RETURN; return $retval" RETURN
it will print 0 regardless of whether retval
is actually set or not, but if you use
trap "return_trap; [ -v retval ] && echo set || echo unset; trap - RETURN; return $retval" RETURN
It will print ‘unset’.
GNU bash, version 4.3.39(1)-release (x86_64-pc-linux-gnu)