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
declare -g retval=$?
in the return_trap()
and use its value in the line setting the trap. The variable is set ([ -v retval ]
returns successfully), but …has no value. Funny.retval=Eeh
to the check_params()
, outside the return_trap()
and just set it to $?
as a usual param. Nope, the retval
in the function doesn’t set the value for the global variable, it stays ‘Eeh’. No, there’s no local
directive. It should be treated as global by default. If you put test=1
to check_params()
and test=3
in check_a_param()
and then print it with echo $test
at the end of setup()
, you should see 3. At least I do. declare -g
doesn’t make any difference here, as expected.return_trap()
along with declare -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
in return_trap()
and read it back with
trap "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)
To set a trap in Bash, use trap followed by a list of commands you want to be executed, followed by a list of signals to trigger it. For complex actions, you can simplify trap statements with Bash functions.
0 means no errors. Any other value means something went wrong; even negative values can be returned. AFAIK there is no other standard for return values except for 0. If you want to return a value that is not 0, it is up to you to tell the users of your program what different values/error codes mean.
The return status can be specified by using the return keyword, and it is assigned to the variable $? . The return statement terminates the function. You can think of it as the function's exit status . #!/bin/bash my_function () { echo "some result" return 55 } my_function echo $?
The secret sauce is a pseudo-signal provided by bash, called EXIT, that you can trap; commands or functions trapped on it will execute when the script exits for any reason. Let's see how this works. The basic code structure is like this: #!/bin/bash.
Funny enough,
trap "return_trap; trap - RETURN" RETURN
simply works.
[ ! -v MANUAL ] && {
check_params; retval2=$?
[ $retval2 -eq 0 ] \
&& echo "check_params() returned with 0. Not running manual setup." \
|| false
}|| ask_for_params
And here’s the trace.
+ check_a_parameter return_bad
+ '[' return_bad = return_ok ']'
+ return 3
+ return 3
++ return_trap
++ local retval=3
++ echo 3
++ '[' 3 -ne 0 ']'
++ echo 'Bad, bad… Dropping to manual setup.'
Bad, bad… Dropping to manual setup.
++ return 3
++ trap - RETURN
+ retval2=3
+ '[' 3 -eq 0 ']'
+ false
+ ask_for_params
+ echo 'User sets params manually step by step.'
User sets params manually step by step.
So the answer is simple: do not try to overwrite the result in the line passed to the trap
command. Bash handles everything for you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With