Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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.

share|improve this question
1  
Try running ls -al $(which sh) to see if your sh command is what you think it is. :-) –  Steven D Apr 28 '11 at 21:36
add comment

3 Answers 3

up vote 8 down vote accepted

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.

share|improve this answer
    
@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? –  Faheem Mitha Apr 28 '11 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. –  Gilles Apr 28 '11 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? –  Faheem Mitha Apr 28 '11 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. –  Gilles Apr 28 '11 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. –  Faheem Mitha Apr 28 '11 at 22:38
show 2 more comments

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
share|improve this answer
1  
grep -v '[^0-9]', or expr, or better, a case expression. –  Gilles Apr 28 '11 at 21:37
    
Sigh. I always use sh for portability, but forgot that this code wasn't portable. Thanks for the clarification. –  Faheem Mitha Apr 28 '11 at 21:40
add comment

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
share|improve this answer
    
This doesn't seem to work. I get sh foo.sh 111 -> '111' has a non-digit somewhere in it –  Faheem Mitha Apr 29 '11 at 8:43
    
@faheem-mitha I've updated answer. –  php-coder Apr 29 '11 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. –  Gilles Apr 29 '11 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. –  Faheem Mitha May 15 '11 at 17:03
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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