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.