3

summary: I'd like to use a bash case statement (in other code) to classify inputs as to whether they are

  • a positive integer
  • a negative integer
  • zero
  • an empty string
  • a non-integer string

Executable code follows, which is correctly classifying the following inputs:

  • ''
  • word
  • a\nmultiline\nstring
  • 2.1
  • -3

but is classifying both the following as ... negative integers :-(

  • 0
  • 42

details:

Save the following to a file (e.g. /tmp/integer_case_statement.sh), chmod it, and run it:

#!/usr/bin/env bash

### This should be a simple `bash` `case` statement to classify inputs as
### {positive|negative|zero|non-} integers.
### Trying extglob, since my previous integer-match patterns failed.
### Gotta turn that on before *function definition* per https://stackoverflow.com/a/34913651/915044
shopt -s extglob

declare cmd=''

function identify() {
    local -r it=${1}  # no quotes in case it's multiline
#    shopt -s extglob # can't do that here
    case ${it} in
        '')
            # empty string, no need for `if [[ -z ...`
            >&2 echo 'ERROR: null arg'
            ;;
        ?(-|+)+([[:digit:]]))
            # it's an integer, so just say so and fallthrough
            >&2 echo 'DEBUG: int(it), fallthrough'
            ;&
        -+([[:digit:]]))
            # it's negative: just return it
            >&2 echo 'DEBUG: int(it) && (it < 0), returning it'
            echo "${it}"
            ;;
        0)
            # it's zero: that's OK
            >&2 echo 'DEBUG: int(it) && (it == 0), returning it'
            echo '0'
            ;;
        ++([[:digit:]]))
            # it's positive: just return it
            >&2 echo 'DEBUG: int(it) && (it > 0), returning it'
            echo "${it}"
            ;;
        *)
            # not an integer, just return it
            >&2 echo 'DEBUG: !int(it)'
            echo "${it}"
            ;;
    esac
} # end function identify

echo -e "'bash --version'==${BASH_VERSION}\n"

echo "identify '':"
identify ''
echo
# > ERROR: null arg

echo 'identify word:'
identify word
echo
# > DEBUG: !int(it)
# > word

echo 'identify a
multiline
string:'
identify 'a
multiline
string'
echo
# > DEBUG: !int(it)
# > a
# > multiline
# > string

echo 'identify 2.1:'
identify 2.1
echo
# > DEBUG: !int(it)
# > 2.1

echo 'identify -3:'
identify -3
echo
# > DEBUG: int(it), fallthrough
# > DEBUG: int(it) && (it < 0), returning it
# > -3

echo 'identify 0:'
identify 0
echo
# > DEBUG: int(it), fallthrough
# > DEBUG: int(it) && (it < 0), returning it
# > 0

echo 'identify 42:'
identify 42
echo
# > DEBUG: int(it), fallthrough
# > DEBUG: int(it) && (it < 0), returning it
# > 42

exit 0

The current output is inlined in the file, but for ease of reading, here's my current output separately:

'bash --version'==4.3.30(1)-release

identify '':
ERROR: null arg

identify word:
DEBUG: !int(it)
word

identify a
multiline
string:
DEBUG: !int(it)
a
multiline
string

identify 2.1:
DEBUG: !int(it)
2.1

identify -3:
DEBUG: int(it), fallthrough
DEBUG: int(it) && (it < 0), returning it
-3

identify 0:
DEBUG: int(it), fallthrough
DEBUG: int(it) && (it < 0), returning it
0

identify 42:
DEBUG: int(it), fallthrough
DEBUG: int(it) && (it < 0), returning it
42

The latter 2 inputs are my problem: why is the case statement identifying

  • 0 as a negative integer (rather than as 0)
  • 42 as a negative integer (rather than as positive)

? Your assistance is appreciated.

3
  • 2
    ;;& (test the other cases fallthrough) instead of ;& (run next case code fallthrough) Jun 17, 2019 at 7:47
  • 1
    ... and make the + optional for positive integers to catch 42 or +42: ?(+)+([[:digit:]]))
    – Freddy
    Jun 17, 2019 at 8:18
  • @frostschutz: You should post that the answer
    – Inian
    Jun 17, 2019 at 8:24

1 Answer 1

2

summary: Thanks to

  • frostschutz for s/;&/;;&/
  • Freddy for the correct positive-integer pattern (guess I just got nightblind)

I also added an additional clause to detect signed zeros, and a few more testcases.

details:

Save this improved code to a file (e.g. /tmp/integer_case_statement.sh), chmod it, and run it:

#!/usr/bin/env bash

### Simple `bash` `case` statement to classify inputs as {positive|negative|zero|non-} integers.
### Trying extglob, since my previous integer-match patterns failed.
### Gotta turn that on before *function definition* per https://stackoverflow.com/a/34913651/915044
shopt -s extglob

declare input=''

### For `case` *patterns* (NOT regexps), see
### https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html
function identify() {
    local -r it=${1}  # no quotes in case it's multiline
#    shopt -s extglob # can't do that here
    case ${it} in
        '')
            # empty string, no need for `if [[ -z ...`
            >&2 echo 'ERROR: null arg'
            ;;
        [+-]0)
            >&2 echo 'ERROR: zero should not be signed'
            ;;
        ?(-|+)+([[:digit:]]))
            # it's an integer, so just say so and fallthrough
            >&2 echo 'DEBUG: int(it), fallthrough'
#            ;& # this only runs the next clause, thanks https://unix.stackexchange.com/users/30851/frostschutz
            ;;& # works
        -+([[:digit:]]))
            >&2 echo 'DEBUG: it < 0'
            ;;
        0)
            >&2 echo 'DEBUG: it == 0'
            echo '0'
            ;;
        ?(+)+([[:digit:]])) # thanks https://unix.stackexchange.com/users/332764/freddy
            >&2 echo 'DEBUG: it > 0'
            ;;
        *)
            >&2 echo 'DEBUG: !int(it)'
            ;;
    esac
} # end function identify

echo -e "'bash --version'==${BASH_VERSION}\n"

for input in \
    '' \
    '@#$%^&!' \
    'word' \
    'a
multiline
string' \
    '2.1' \
    '-3' \
    '+3' \
    '+0' \
    '0' \
    '-0' \
    '42' \
; do
    echo "identify '${input}'"
    identify "${input}"
    ret_val="${?}"
    if [[ "${ret_val}" -ne 0 ]] ; then
        >&2 echo "ERROR: retval='${ret_val}', exiting ..."
        exit 3
    fi
    echo # newline
done

exit 0

On this Debian workstation, the above currently outputs:

'bash --version'==4.3.30(1)-release

identify ''
ERROR: null arg

identify '@#$%^&!'
DEBUG: !int(it)

identify 'word'
DEBUG: !int(it)

identify 'a
multiline
string'
DEBUG: !int(it)

identify '2.1'
DEBUG: !int(it)

identify '-3'
DEBUG: int(it), fallthrough
DEBUG: it < 0

identify '+3'
DEBUG: int(it), fallthrough
DEBUG: it > 0

identify '+0'
ERROR: zero should not be signed

identify '0'
DEBUG: int(it), fallthrough
DEBUG: it == 0
0

identify '-0'
ERROR: zero should not be signed

identify '42'
DEBUG: int(it), fallthrough
DEBUG: it > 0

Your assistance is appreciated!

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .