checking an argument to a bash script is a string of all digits
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
Related videos on Youtube
Alen Milakovic
Updated on September 18, 2022Comments
-
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 likeif [[ $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 about 13 yearsTry running
ls -al $(which sh)
to see if yoursh
command is what you think it is. :-) -
Admin almost 9 years
[[
is non-standard and makes a script non-portable
-
-
Gilles 'SO- stop being evil' about 13 years
grep -v '[^0-9]'
, orexpr
, or better, acase
expression. -
Alen Milakovic about 13 yearsSigh. I always use
sh
for portability, but forgot that this code wasn't portable. Thanks for the clarification. -
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' 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 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' 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 called2+2
or ` ` (a space). For extra credit, try calling the script-
(a single dash), in various shells. -
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-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. -
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 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 about 13 yearsThis doesn't seem to work. I get
sh foo.sh 111
->'111' has a non-digit somewhere in it
-
Slava Semushin about 13 years@faheem-mitha I've updated answer.
-
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 usingexpr
(I can't think of a good reason wherecase
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 about 13 yearsupvoted. 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 almost 9 yearsBash is not a Bourne Shell
derivate
but a reimplementation with mostly Bourne Shell like behavior. TheKorn Shell
on the other side is a true Bourne Shell derivate as David Korn started his development with the Bourne Shell source.