checking an argument to a bash script is a string of all digits

17,374

Solution 1

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.

Solution 2

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:
17,374

Related videos on Youtube

Alen Milakovic
Author by

Alen Milakovic

Updated on September 18, 2022

Comments

  • Alen Milakovic
    Alen Milakovic almost 2 years

    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.

    • Admin
      Admin about 13 years
      Try running ls -al $(which sh) to see if your sh command is what you think it is. :-)
    • Admin
      Admin almost 9 years
      [[ is non-standard and makes a script non-portable
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' about 13 years
    grep -v '[^0-9]', or expr, or better, a case expression.
  • Alen Milakovic
    Alen Milakovic about 13 years
    Sigh. I always use sh for portability, but forgot that this code wasn't portable. Thanks for the clarification.
  • Alen Milakovic
    Alen Milakovic about 13 years
    @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?
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' about 13 years
    @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.
  • Alen Milakovic
    Alen Milakovic about 13 years
    @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?
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' about 13 years
    @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.
  • Alen Milakovic
    Alen Milakovic about 13 years
    @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-Construct‌​s 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.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' about 13 years
    @Faheem: The echo lines are just examples, similar to your question. The case construct first tries to match the wildcard pattern *[!0-9]* (anything followed by a non-digit followed by anything), then if it doesn't match tries * (anything). The patterns are usual shell wildcards, with the same meaning as when globbing files.
  • Alen Milakovic
    Alen Milakovic about 13 years
    @Gilles: Ok. I was confused about whether it was using regex or shell wildcards. I presume the section pubs.opengroup.org/onlinepubs/009695399/utilities/… in the POSIX standard you linked to specifies shell wildcards, though that is not very clear. Thanks.
  • Alen Milakovic
    Alen Milakovic about 13 years
    This doesn't seem to work. I get sh foo.sh 111 -> '111' has a non-digit somewhere in it
  • Slava Semushin
    Slava Semushin about 13 years
    @faheem-mitha I've updated answer.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' about 13 years
    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.
  • Alen Milakovic
    Alen Milakovic about 13 years
    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.
  • schily
    schily almost 9 years
    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.