5

The Bash FAQ says

If you're validating a simple "string of digits", you can do it with a glob:

# Bash
if [[ $foo = *[!0-9]* ]]; then
    echo "'$foo' has a non-digit somewhere in it"
else
    echo "'$foo' is strictly numeric"
fi

I thought, "Goody, that looks nice and simple". I then pasted exactly that into a script, except I added "exit 1" after the first echo and changed $foo to $1, so that it looked like

if [[ $1 = *[!0-9]* ]]; then
    echo "'$1' has a non-digit somewhere in it"
    exit 1
else
    echo "'$1' is strictly numeric"
fi

I then tried to run this and got

$ sh foo.sh bar
bar
foo.sh: 6: [[: not found
'bar' is strictly numeric

I'm Bash illiterate, I'm ashamed to say, so I've no idea what could be wrong here. I had the impression, supported by the online Bash manual that the operator for matching with regexes is =~, but changing that doesn't make any difference. And that [[ operator that seems to be problematic here looks standard, though I don't know what the difference is between [[ ]] and [ ], which both correspond to testing the expression, as far as I know. I'm using Debian squeeze with bash

$ bash --version
GNU bash, version 4.1.5(1)-release (i486-pc-linux-gnu)

Debian says version 4.1-3.

2
  • 1
    Try running ls -al $(which sh) to see if your sh command is what you think it is. :-)
    – Steven D
    Apr 28, 2011 at 21:36
  • 1
    [[ is non-standard and makes a script non-portable
    – schily
    Sep 6, 2015 at 13:34

3 Answers 3

9

Why are you calling sh, if that's a bash script? It's clear that on your system, sh is not bash, but some other shell in the Bourne/POSIX family. In fact, it's dash, a smaller shell designed for low memory consumption and speed that pretty much only supports POSIX constructs and built-in utilities.

[[ … ]] is a ksh extension to the Bourne syntax that was picked up by bash and zsh but not by POSIX. In a portable script, you need to use [ … ] for tests. The standard construct doesn't have any support for pattern matching; the standard idiom is to use a case construct:

case $1 in                        # branch to the first pattern that $1 matches
  *[!0-9]*)                       # pattern = anything containing a non-digit
    echo not a number             # do this if the first pattern triggered
    ;;                            # end of this case branch
  *)                              # pattern = anything (else)
    echo successor of $(($1-1))   # do this if the second pattern triggered
    ;;                            # end of this case branch
esac                              # end of the case construct

Here's a function that tests if its argument is all-digits:

is_all_digits () {
  case $1 in *[!0-9]*) false;; esac
}

Digression: I initially made a typo in the snippet above: I'd written $(($0-1)). This caused odd-looking error messages:

$ ash foo.sh 42
foo.sh: 4: arithmetic expression: expecting EOF: "foo.sh-1"
$ ash ./foo.sh 42
./foo.sh: 4: arithmetic expression: expecting primary: "./foo.sh-1"
$ ksh ./foo.sh 42
./foo.sh: line 3: foo.sh: invalid variable name
$ pdksh ./foo.sh 42
./foo.sh[4]: ./foo.sh-1: unexpected `.'
$ bash foo.sh 42         
foo.sh: line 3: foo.sh-1: syntax error: invalid arithmetic operator (error token is ".sh-1")
$ bash ./foo.sh 42
./foo.sh: line 3: ./foo.sh-1: syntax error: operand expected (error token is "./foo.sh-1")
$ zsh foo.sh 42
foo.sh:3: bad floating point constant

$0 is the name of the script, so the arithmetic expression to be evaluated was foo.sh-1 or ./foo.sh-1. You can watch the diversity of error mesages amongst shells. I was a little surprised to see that ash's messages and bash's message without ./ were the clearest: none of the other shells mention that the problem is in an arithmetic expression. Ash and pdksh do get docked points for reporting the error one line too far.

7
  • @Gilles: I'm using sh for a bash script because I'm a moron. :-) So both [[ ]] and [ ] work for tests in bash, but in general only [ ] works? Apr 28, 2011 at 21:42
  • @Faheem: Bash/ksh/zsh have both [[ and [, other shells have only [. [[ and [ have a lot of operators in common, but [[ is a special shell construct whereas [ has to obey normal quoting rules. Apr 28, 2011 at 21:48
  • @Gilles: With your alternative syntax above, I'm getting: $ sh foo.sh 100 foo.sh: 4: arithmetic expression: expecting EOF: "foo.sh-1". Can you break down the syntax in the above shell snippet a little bit for us illiterates, please? Apr 28, 2011 at 21:51
  • @Faheem: Typo, see my edit. The script took the right case branch, but then the arithmetic expression $(($0-1)) bombed out, because $0 (the name of the script) looked nothing like a number. Just for fun, try the original version with a script called 2+2 or ` ` (a space). For extra credit, try calling the script - (a single dash), in various shells. Apr 28, 2011 at 22:11
  • @Gilles: I see. I don't need the echo lines, right? So I can do case $1 in *[!0-9]*) exit 1;; esac. I see that gnu.org/software/bash/manual/bash.html#Conditional-Constructs has a section on case. perhaps add this link, unless you can find a better one? So this matches the argument against the regex *[!0-9]*? And the * line I assume is executed if all matches fail, though I don't know what the * denotes here. Apr 28, 2011 at 22:38
3

Try running it with bash foo.sh bar. If your going to write a bash script, you need to use bash. The [[ and ]] used above are only in bash which is a Bourne shell derivative. sh means Bourne SHell and may not be the same as bash. I think debian uses dash for sh. If you instead want to learn how to write portable Bourne shell scripts that don't require bash specific features, you can rewrite that using grep instead:

if echo "$foo" | grep '^[0-9][0-9]*$' >/dev/null 2>&1; then
    echo "'$1' has a non-digit somewhere in it"
    exit 1
else
    echo "'$1' is strictly numeric"
fi
3
  • 1
    grep -v '[^0-9]', or expr, or better, a case expression. Apr 28, 2011 at 21:37
  • Sigh. I always use sh for portability, but forgot that this code wasn't portable. Thanks for the clarification. Apr 28, 2011 at 21:40
  • Bash is not a Bourne Shell derivatebut a reimplementation with mostly Bourne Shell like behavior. The Korn Shellon the other side is a true Bourne Shell derivate as David Korn started his development with the Bourne Shell source.
    – schily
    Sep 6, 2015 at 13:37
0

I suggest to use expr because it is POSIX-compliant:

if [ "$(expr "$1" : '^[0-9]\+$')" = "0" ]; then
    echo "'$1' has a non-digit somewhere in it"
    exit 1
else
     echo "'$1' is strictly numeric"
fi
4
  • This doesn't seem to work. I get sh foo.sh 111 -> '111' has a non-digit somewhere in it Apr 29, 2011 at 8:43
  • @faheem-mitha I've updated answer. Apr 29, 2011 at 13:58
  • case is also POSIX-compliant, and it's a shell built-in construct. If you insist on using expr (I can't think of a good reason where case is enough, i.e. you can make do with wildcards), use its return result: if expr "$1" : '[0-9]*' >/dev/null; then … Note also that your regexp is not portable: expr matches are always anchored, and some implementations treat ^ and $ literally. Apr 29, 2011 at 18:08
  • upvoted. sorry for the long delay, I forgot about this question. If you care to, put in a link about expr usage. I don't think I've never used it. May 15, 2011 at 17:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .