<<< Back to Tips Index

18th Oct 2017

Return codes, Functions, and the number 255

The problem with using shell functions to return integers

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 rename nameless):

Invest in your career. Buy my Shell Scripting Tutorial today:

 

Steve's Bourne / Bash shell scripting tips
Share on Twitter Share on Facebook Share on LinkedIn Share on Identi.ca Share on StumbleUpon