My POSIX is_integer () function looks like this for a long time:

is_integer ()
    [ "$1" -eq "$1" ] 2> /dev/null

However, today, I found it broken. If there are some spaces around the number, it surprisingly also evaluates to true, and I have no idea how to fix that.

Example of correct (expected) behavior:

is_integer 123 evaluates to true.

Example of incorrect (unexpected) behavior:

is_integer ' 123' also evaluates to true, however it obviously contains a leading space, thus the function is expected to evaluate to false in such cases.

POSIX-compliant suggestions only, please. Thank you.

  • 1
    Note that even in the absence of spaces, you couldn't use that to detect integers in ksh, since extends the interpretation of the arguments of -eq quite a lot wider than to just integers. Stuff like abc (the value on variable abc), 12.345 (floating point), 1+1 (arithmetic expression) get accepted. – ilkkachu Jul 12 at 10:13
is_integer ()
    case "${1#[+-]}" in
        (*[!0123456789]*) return 1 ;;
        ('')              return 1 ;;
        (*)               return 0 ;;

Uses only POSIX builtins. It is not clear from the spec if +1 is supposed to be an integer, if not then remove the + from the case line.

It works as follows. the ${1#[+-]} removes the optional leading sign. If you are left with something containing a non digit then it is not an integer, likewise if you are left with nothing. If it is not not an integer then it is an integer.

Edit: change ^ to ! to negate the character class - thanks @LinuxSecurityFreak

| improve this answer | |
  • I don't get why ^ was changed to !. Isn't ^ used to negate the class? See RE bracket expressions – Quasímodo Jul 12 at 11:34
  • True! Just for the record, here the exception is documented in Shell Command Language, Pattern Matching Notation. – Quasímodo Jul 12 at 11:43
  • Your tests should have some multi-digit strings in them. You might want to extend the patterns to reject leading zeros (accept '0', reject '0'*). – icarus Jul 12 at 21:57
  • @icarus. You can replace [!0123456789] with a POSIX class, i.e. [![:digit:]] – fpmurphy Jul 12 at 22:43
  • 1
    @fpmurphy That is true that you can use [:digit:] but I would rather not have 123๔ as an integer because the last character is a digit 4 in Thai. In the original version of this post I use [^0-9] but changed it to explicitly list the characters I wanted to use in the definition of an integer. – icarus Jul 12 at 22:54

Not the most efficient (due to the external command), but quite simple:

is_integer () {
  expr "X$1" : "X-\{0,1\}[0-9][0-9]*$" > /dev/null

At least in the implementation I am testing, an initial argument - is treated not as part of a matching operation, but apparently as part of an invalid arithmetic expression; the X ensures expr parses its arguments as a valid match operation.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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