The Shell Scripting Tutorial


Return codes, Functions, and the number 255

The problem with using shell functions to return integers

18 Oct 2017

In most languages, you would write a function to return the square of an integer like this:

private int square(int n) {
  return n*n;
}

This Java code would work fine; The Shell script would look like this; I've added a couple of test cases, too:

#!/bin/bash

square() {
  return $(($1 * $1))
}

for n in 4 8 15 16 17
do
  echo -n "The square of $n is: "
  square $n
  echo $?  # The return code is passed back as $?
done
exit 12345
download square.sh

The Shell script would print these results:

$ ./square.sh
The square of 4 is: 16
The square of 8 is: 64
The square of 15 is: 225
The square of 16 is: 0
The square of 17 is: 33
$ echo $?
57
$

The observant reader will have spotted that it works fine up to, and including, 15*15 = 225.

16*16 is clearly not zero; it is 256.

And 17*17 is 289, not 33.

These wrong results are 256 below their expected values. That's because the return code from a shell function (and from any Unix/Linux program) is a single byte; it has a value between 0 and 255. So 256 gets wrapped around to zero again. 289 is 33 more than 256, so it gets wrapped to 33.

Because the whole script ends with "exit 12345", the return code from the script is 57 (that's 12345 modulo 256, aka 12345%256). But not 12345.

The Solution

Oddly, the solution here, is to acknowledge that the shell really treats all variables as strings, until it is forced to do otherwise - for example, when it is forced to make a numerical comparison, such as:
  if [ "$a" -gt "$b" ]; then ...

So if you echo the result back as a string, this square function will work fine.

In the code below, the syntax: "$(( expression ))" tells the shell to evaluate expression, and return the result.

(It would also be possible (and probably more compatible across shell implementations) to use: "expr $1 \* $1" (the "*" has to have the backslash, or the shell would interpret it as 'all filenames in the current directory'), but that does involve spawning the external expr program, rather than keeping it all in the bash process.)

In the main code which calls the square function, instead of the result coming to the variable "$?", it is returned as standard output, so we catch its content by saying: "result=$(square $n)" - this catches the output and stores it in the $result variable.

#!/bin/bash

square() {
  echo $(($1 * $1))
}

for n in 4 8 15 16 17
do
  echo -n "The square of $n is: "
  result=$(square $n)
  echo $result  # The return code is passed back as $?
done

exit 12345
download square2.sh

The return code from the script, 12345, will only ever be seen as 57, because that is how Unix (and therefore Linux) works. The return code from a program is only ever a single byte.

And it's all smarter than this Java implementation by some ill-informed Java coder, as found somewhere on the internet (the guilty shall remain nameless):


My Paperbacks and eBooks

My Shell Scripting books, available in Paperback and eBook formats. This tutorial is more of a general introduction to Shell Scripting, the longer Shell Scripting: Expert Recipes for Linux, Bash and more book covers every aspect of Bash in detail.

Shell Scripting Tutorial

Shell Scripting Tutorial
is this tutorial, in 88-page Paperback and eBook formats. Convenient to read on the go, and in paperback format good to keep by your desk as an ever-present companion.

Also available in PDF form from Gumroad:Get this tutorial as a PDF
Shell Scripting: Expert Recipes for Linux, Bash and more

Shell Scripting: Expert Recipes for Linux, Bash and more
is my 564-page book on Shell Scripting. The first half covers all of the features of the shell in every detail; the second half has real-world shell scripts, organised by topic, along with detailed discussion of each script.