13

What would be the best way to check whether $1 is an integer in /bin/dash ?

In bash, I could do:

[[ $1 =~ ^([0-9]+)$ ]]

But that does not seem to be POSIX compliant and dash does not support that

Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
Martin Vegter
  • 69
  • 66
  • 195
  • 326

7 Answers7

13

The following detect integers, positive or negative, and work under dash and are POSIX:

Option 1

echo "$1" | grep -Eq '^[+-]?[0-9]+$' && echo "It's an integer"

Option 2

case "${1#[+-]}" in
    ''|*[!0-9]*)
        echo "Not an integer" ;;
    *)
        echo "Integer" ;;
esac

Or, with a little use of the : (nop) command:

! case ${1#[+-]} in *[!0-9]*) :;; ?*) ! :;; esac && echo Integer
mikeserv
  • 57,448
  • 9
  • 113
  • 229
John1024
  • 73,527
  • 11
  • 167
  • 163
5

Whether dash, bash, ksh, zsh, POSIX sh, or posh ("a reimplementation of the Bourne shell" sh) ; the case construct is the most widely available and reliable:

case $1 in (*[!0-9]*|"") false ;; (*) true ;; esac
Janis
  • 14,014
  • 3
  • 25
  • 42
  • Did you test this under `dash`? It works for me under `bash` but not `dash`. – John1024 Apr 23 '15 at 21:50
  • Yes, I tested it on my system also with `dash`; to interrogate the result I added `echo $?` after the case command. – Janis Apr 23 '15 at 21:53
  • I did the same (Debian stable) but no joy. For a complete example that works for me under my dash, see my _Option 2_. – John1024 Apr 23 '15 at 21:55
  • Hmm, I have no original bourne shell available to test. The docs that I inspected about "features [in ksh] not in bourne shell" at least does not mention it, so I assume it was there. No? - Then omit the leading parenthesis. - OTOH, `posh` ("a reimplementation of the Bourne shell") has also no problem with that solution, though. – Janis Apr 23 '15 at 22:09
  • @Janis - yeah, `posh` handles it correctly because it is POSIX, but ye olde Bourne shell predates the standard - and `(pattern)`. That said, *(and as Stéphane taught me)* other shells fail to handle `pattern)` correctly when the `case` statement is enclosed in `$(`cmdsub`)` parens. Try `: "$(case $PWD in broken) :;;esac)"` in `zsh`, for example. – mikeserv Apr 24 '15 at 03:23
  • 1
    @mikeserv; There are several reasons why I use always matching parentheses in `case`; one reason is the bug you describe, another one that in editors that have features that rely on matching parenthesis (vim) it gives much better support, and not least I find it personally better legible to have them matching. - WRT `posh` being POSIX; well, the quote from the man page that I gave suggested something else, but one can't rely on such informal statements anyway, I guess. Old bourne shell is anyway not that significant any more now that we're in POSIX era. – Janis Apr 24 '15 at 08:13
2

You can use the -eq test on the string, with itself:

$ dash -c 'a="a"; if [ "$a" -eq "$a" ] ; then echo number; else echo not a number; fi' 
dash: 1: [: Illegal number: a
not a number
$ dash -c 'a="0xa"; if [ "$a" -eq "$a" ] ; then echo number; else echo not a number; fi'
dash: 1: [: Illegal number: 0xa
not a number
$ dash -c 'a="-1"; if [ "$a" -eq "$a" ] ; then echo number; else echo not a number; fi'
number

If the error message is a problem, redirect error output to /dev/null:

$ dash -c 'a="0xa"; [ "$a" -eq "$a" ] 2>/dev/null|| echo no'
no
muru
  • 69,900
  • 13
  • 192
  • 292
  • 1
    It would also say that `" 023 "` is a number. Note that it works with dash, but not all other POSIX shells as the behaviour is unspecified if the operands are note decimal integers. For instance with ksh, it would say that `SHLVL` or `1+1` is a number. – Stéphane Chazelas Apr 23 '15 at 22:06
1

In POSIX system, you can use expr:

$ a=a
$ expr "$a" - 0 >/dev/null 2>&1
$ [ "$?" -lt 2 ] && echo Integer || echo Not Integer
cuonglm
  • 150,973
  • 38
  • 327
  • 406
  • It will say that 0 is not an integer (and say -12 is which the OP's bash solution would have rejected). Some `expr` implementations will say that 9999999999999999999 is not an integer. POSIX gives no warranty that this will work. In practice, on a GNU system at least, it will say that "length" is an integer. – Stéphane Chazelas Apr 24 '15 at 09:54
  • In my test, it works at least in Debian and OSX. It does say integer for 0. Posix ensure $a must be a valid expression (integer) for expr do arithmetic. – cuonglm Apr 24 '15 at 10:18
  • Oh yes, sorry, I had overlooked your test for $? < 2. Still `expr 9999999999999999999 + 0` gives me a 3 exit status and `expr -12 + 0` and `expr length + 0` give me a 0 exit status with GNU expr (`+ string` forces `string` to be considered as a string with GNU `expr`. `expr "$a" - 0` would work better). – Stéphane Chazelas Apr 24 '15 at 10:21
  • @StéphaneChazelas: Oh, yes, updated my answer. I think `-12` is a valid integer, and `9999999999999999999` gave an overflow. – cuonglm Apr 24 '15 at 11:38
0

Try using it as an arithmetic expansion, and see if it works. Actually, you need to be a bit stricter than that, because arithmetic expansions would ignore leading and trailing spaces, for instance. So do an arithmetic expansion, and make sure that the expanded result matches the original variable exactly.

check_if_number()
{
    if [ "$1" = "$((${1}))" ] 2>/dev/null; then
        echo "Number!"
    else
        echo "not a number"
    fi
}

This would also accept negative numbers - if you really mean to exclude them, add an extra check for $((${1} >= 0)).

godlygeek
  • 7,963
  • 1
  • 28
  • 28
  • 1
    dash does not have `[[` – glenn jackman Apr 23 '15 at 21:20
  • @glennjackman Whoops. Does it have `$(( ... ))` ? If so, my answer should still be materially correct, I just need to add some extra quoting. – godlygeek Apr 23 '15 at 21:21
  • it's your answer: go find out. – glenn jackman Apr 23 '15 at 21:22
  • Looks like it does, so my updated version should do the trick. I don't have dash to test it out, though - so let me know if I missed anything else. – godlygeek Apr 23 '15 at 21:23
  • I tried: `check_if_number 1.2` and the function returned: `dash: 3: arithmetic expression: expecting EOF: "1.2"` – John1024 Apr 23 '15 at 21:30
  • That would say 01 is not a number. – Stéphane Chazelas Apr 23 '15 at 21:31
  • @John1024, odd - I would have expected the 2>/dev/null to squelch that message. I guess I'll have to test with dash later. – godlygeek Apr 23 '15 at 21:36
  • `2> /dev/null` only redirects the errors of the `[` command, not that of the expansion of `$((...))` used to construct that `[` command line. – Stéphane Chazelas Apr 23 '15 at 21:41
  • Ah well. I'll admit that using "case" is a better answer than this, in any event. – godlygeek Apr 23 '15 at 21:50
  • Do `ck_int() { [ "$1" -eq "$(($1))" ]; } 2>/dev/null` and it will return correctly without making any noise - because the stderr for the `{` compound command `}` in which the math expansion occurs is redirected. But a bad math expansion will kill a non-interactive shell and may be dangerous for other reasons *(try `ck_int PATH=0`)*. Instead - in `dash` at least - do `ck_int(){ [ "${1##*[!0-9]}" -eq "${1#[+-]}" ]; } 2>/dev/null` and you will get a silent boolean test which returns 0 for true or other than 0 for false. – mikeserv Apr 24 '15 at 04:19
0

Perhaps with expr?

if expr match "$1" '^\([0-9]\+\)$' > /dev/null; then
  echo "integer"
else 
  echo "non-integer"
fi
steeldriver
  • 78,509
  • 12
  • 109
  • 152
0

Here's a simple function using the same method as muru's answer:

IsInteger()      # usage: IsInteger string
{               #  returns: flag
        [ "$1" -eq "$1" ] 2> /dev/null
}

Example:

p= n=2a3; IsInteger $n || p="n't" ; printf "'%s' is%s an integer\n" "$n" "$p"
p= n=23;  IsInteger $n || p="n't" ; printf "'%s' is%s an integer\n" "$n" "$p"

Output:

'2a3' isn't an integer
'23' is an integer
agc
  • 7,045
  • 3
  • 23
  • 53