Functions: Return Values
Return status
Every shell function returns its status in $?
which is an eight bit value.
That it is only eight bit can be seen in this experiment:
foo()
{
return $1
}
bar()
{
$( foo $1 )
printf "%s\n" "foo( $1) = $?"
}
bar -256
bar -255
bar -128
bar -127
bar -1
bar 0
bar 127
bar 128
bar 255
bar 256
yields
foo( -256) = 0
foo( -255) = 1
foo( -128) = 128
foo( -127) = 129
foo( -1) = 255
foo( 0) = 0
foo( 127) = 127
foo( 128) = 128
foo( 255) = 255
foo( 256) = 0
A zero in the return status indicates “success”, so you have 255 possible values for other conditions. Some of the values like 127 have a predefined meaning of “executable not found”.
If you need to distinguish multiple return codes, you can do so easily with
a case
statement:
local rc
call-unknown-command
rc=$?
case $rc in
0)
# OK
;;
127)
printf "%s\n" "command call-unknown-command not found" >&2
exit 1
;;
*)
printf "%s\n" "failure $rc" >&2
exit 1
;;
esac
Standard I/O
Standard output from a function is easily captured with $()
:
foo()
{
printf "%s\n" "You can call me $BASHPID"
}
bar()
{
local x
x=$(foo)
printf "%s\n" "$BASHPID: $x"
}
bar
8903 You can call me "138193"
Compared to using the status as the return value, the return value can be
arbitrarily large now. As a bonus it can be piped to another function.
(e.g. foo | wc-l
). This opens up opportunities for parallelization.
Using standard output as the return value works nicely even for very large values (Gigabytes). But it comes at the cost of spawning a subshell. Compared to a simple function call, subshell spawning is very expensive. It may be prohibitively expensive for small functions that get called often.
Note
Actually a function now has two return values as the status is still there and should be checked:
x=$(foo) || exit 1
.
Global variable
Lets define a global variable RVAL, that works akin to $?
. It keeps the
return value of a function, until it is reused by another function. So you
should immediately assign "$RVAL"
to a local variable in your calling function.
Functions adhering to this standard I prefix with r_
. The convention is, that
a r_
function will not return a value via standard output. Success or
failure of such a function is still indicated by $?
. It is expected that
RVAL
is set to empty in all error cases.
r_foo()
{
RVAL="You can call me $BASHPID"
}
bar()
{
local x
if r_foo
then
x="${RVAL}"
printf "%s\n" "$BASHPID: $x"
fi
}
bar
8903: You can call me 8903
As you can see, no subshell was spawned.
A downside of RVAL
is, that there is no way to pipeline such a function.
I use r_
functions all the time for functions that return small amounts of
data. I wouldn’t want to write larger shells scripts without this convention.