This one caught me unawares!

We have a Solaris machine (version: SunOS 5.8) hosting a pivotal script/process which has been running for many years without complaint. We recently had cause to have a look under the hood and found a test condition that -- as far as I can tell -- shouldn't work, but for some reason does:

$ sh
$ [ 90% -gt 95 ]; echo $?
1
$ [ 96% -gt 95 ]; echo $?
0

What?! It doesn't appear to be a fluke:

$ [ 96% -lt 95 ]; echo $?
1
$ [ 96% -eq 96 ]; echo $?
0   

It's not just the % either:

$ [ 96blah -eq 96 ]; echo $?
0
$ [ 1.2 -gt 1 ]; echo $?
1

It appears to be like this on SunOS 5.8, 5.9 and 5.10 but I otherwise haven't noticed this before. Unfortunately, I don't appear to have another non-Solaris system easily at hand to check that doesn't have /bin/sh symlinked to bash (which isn't so liberal with its interpretation).

So, sloppy code aside (the x% is passed by variable and probably wasn't noticed), why is the standard (presumably Bourne?) shell treating these test arguments as integers by truncating them? Is this documented behaviour? I couldn't see anything in man sh but may have missed something.

NB: This was actually a secondary boo-boo within another strange test condition, but that probably deserves its own question.

  • 2
    I'm not sure about traditional Bourne shell. With heirloom sh, it convert string to integer before compare, using stoifll function in print.c file. – cuonglm Jun 30 '15 at 17:49
  • "... /bin/sh symlinked to bash ..."?!?!?! Just to be sure, that's not a joke? – Andrew Henle Jun 30 '15 at 18:02
  • 3
    Here's the Solaris source for test.c that confirms what @cuonglm said. github.com/illumos/illumos-gate/blob/master/usr/src/cmd/sh/… . The shell calls strtoll but doesn't check that the entire string is a valid number. – Mark Plotnick Jun 30 '15 at 19:30
  • Thank you both, that appears to explain it. @AndrewHenle - Not a joke!! – AlexC Jul 1 '15 at 11:06
  • 2
    If you like to know how the original source looks, it is better to check the OpenSolaris mercurial. If you like to understand differences to other shells, it is better to look at the schilytools, as this is actively maintaned and as it contains a portable version of the OpenSolaris based shell. In contrary to heirloom, it e.g. uses malloc() instead of sbrk() and thus works on Cygwin. As this is an actively developed shell, you may look at the #ifdefs and the related documentation in the Makefile to lean about differences. See: sourceforge.net/projects/schilytools/files – schily Sep 2 '15 at 13:18
up vote 0 down vote accepted

The standard says that test operators like -gt compare it's operands as integers, but it does not mention what should happen when the supplied strings are no integers. So the observed behavior is fully correct. BTW: it is also compatible with historic UNIX behavior.

/bin/sh of course is not linked to bash, as bash is not standard compliant enough and probably would cause a lot of scripts to fail.

In 2010, with the development of Solaris 10, /bin/sh was replaced by ksh93 and even though ksh is much more compatible to the Bourne Shell than bash, this caused Solaris to be unable to have / and /usr in different filesystems which was no problem before. Note that ksh93 loads dynamic libraries from /usr.

  • It's also consistent with atoi(), strtod() and other tools that get numbers out of strings (sort -n, awk '{print 0+$1}'...) – Stéphane Chazelas Sep 3 '15 at 10:46
  • 1
    Note that bash is used as the /bin/sh of at least one certified Unix compliant system (OS/X though /bin/sh there is bash built with strict compliance enabled). I'd doubt many POSIX scripts would be broken by a sh being a symlink to bash though. – Stéphane Chazelas Sep 3 '15 at 10:49
  • This certification does not mean anything as in special the shell tests from the POSIX certification seem to be very incomplete. OS/X did already get a POSIX cetification when the only shell on OS/X was bash-3 and bash-3 is definitely not POSIX at all. See e.g. the completely wrong treatment of sh -ce 'cmd' with bash-3. – schily Sep 3 '15 at 11:03
  • As already discussed, all shells have conformance bugs. I'm not under the impression that bash has more than ksh93, at least not in the functionality that are most likely to be exerted by most POSIX scripts. set -e is one feature I avoid (and prefer explicit error handling. IMO relying on it in a script is bad practice, makes sense for make, but then that's generally one simple command at a time). If you're referring to the behaviour for bash -ec '(false; echo A); echo B', I believe it was fixed in bash-4.0. – Stéphane Chazelas Sep 3 '15 at 11:18
  • Note that sh -ce 'cmd'is a very special case as implementing it incorrectly makes make behave incorrect. Two notes: 1) your script is not complex enough to show the problem (see the my autoconf test for a complex enough case). 2) It took me and David Korn more than a month of email-discussions with the bash maintainer to convince him to fix bash. – schily Sep 3 '15 at 11:24

As I cannot format a comment into intended lines, I use the answer form to explain my comment about how to get a POSIX shell.

The official method to get a POSIX shell is:

1)  unset PATH                       # if you run an older Bourne Shell
2a) PATH=`getconf PATH`              # this is with the Bourne Shell
2b) PATH=$(command -p getconf PATH)  # this is with a Korn shell
3)  sh                               # This starts a POSIX shell

There have been several attempts to standardize #!/path/to/shell but POSIX does not deal with PATHs and for this reason, we could not find a solution.

Looks like this was answered in comments: Solaris sh has quirks. At least in the past, it wasn't even a POSIX shell. (Schily points out that POSIX doesn't require /bin/sh to be POSIX sh, which means you have to jump through another hoop to write fully-portable shell scripts.)

This question may have some useful info/links: https://superuser.com/questions/125728/what-is-the-difference-between-bash-and-sh

Schily's answer points out that the standard doesn't specify what happens to numbers with trailing garbage, so your script is effectively has undefined behaviour, and relies on what Solaris sh does. Thus, it's not portable.

Things like this are the major reason it's apparently not recommended to use bash as /bin/sh on a Solaris system. Some GNU/Linux distros use dash for /bin/sh, while others use bash.

I tested your code on 3 shells I have installed:

bash  4.3-11ubuntu2
$ [ 96blah -eq 96 ]; echo $?
bash: [: 96blah: integer expression expected
2

dash  0.5.7-4ubuntu1
$ [ 96blah -eq 96 ]; echo $?
dash: 1: [: Illegal number: 96blah
2

busybox sh  1.22.0-9ubuntu1
$ [ 96blah -eq 96 ]; echo $?
sh: 96blah: bad number
2
  • If you mention "Solaris sh was not even POSIX", you verify that you miss the POSIX standard. POSIX does not require that /bin/sh is POSIX at all.instead there is a documented method to get a POSIX shell, see answer below – schily Sep 3 '15 at 9:37
  • BTW: there is no portable compile method for dash, so it seems to be limited to Linux. Even it's upstream ash seems to exist only inside a BSD source tree. My bsh (not derived from Bourne) prints Not a number: 96blah. – schily Sep 3 '15 at 9:50

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.