3

I am trying to test that an infinite number of arguments ( "$@" ) to a bash script are numbers ( "#", "#.#", ".#", "#.") delimited by spaces (i.e. # # # # ...). I have tried:

[ "$@" -eq "$@" ]

similar to what I found in this answer but I get:

"[: too many arguments"

and I have also tried regular expressions but it seems once the regular expression is satisfied anything can come afterwards. here is my code:

if (($# >=1)) && [[ "$@" =~ ^-?[[:digit:]]*\.?[[:digit:]]+ ]]; then

it also needs to not allow "#.." or "..#"

Community
  • 1
  • 1
Kyuvi
  • 360
  • 3
  • 13
  • Assuming that the syntax was correct (it isn't), why *wouldn't* something equal itself? – chepner Dec 18 '16 at 13:29
  • @chepner I completely missed that test only works on integers, but using "-eq" can test for integers (as opposed to letters, etc) as explained in [this answer](http://stackoverflow.com/a/808740/6713615). I was trying to get it to work on an infinite number of parameters. – Kyuvi Dec 18 '16 at 17:21

4 Answers4

2

I don't think that [ "$@" -eq "$@"] is going to work somehow.

A loop like this could help to read each argument and detect if it is an integer number (bash does not handle decimals):

for i in $@;do
if [ "$i" -eq "$i" ] 2>/dev/null
then
    echo "$i is an integer !!"
else
    echo "ERROR: not an integer."
fi
done

In your case , to determine if argument is a valid integer/decimal number instead of all those regex ifs, we can simply divide the number with it's self using bc program of bash.
If it is a valid number will return 1.00

So in your case this should work:

for i in $@;do
if [[ "$(bc <<< "scale=2; $i/$i")" == "1.00" ]] 2>/dev/null;then
    echo "$i is a number and thus is accepted"
else 
    echo "Argument $i not accepted"
fi
done

Output:

root@debian:# ./bashtest.sh 1 3 5.3 0.31 23. .3 ..2 8..
1 is a number and thus is accepted
3 is a number and thus is accepted
5.3 is a number and thus is accepted
0.31 is a number and thus is accepted
23. is a number and thus is accepted
.3 is a number and thus is accepted
Argument ..2 not accepted
Argument 8.. not accepted
George Vasiliou
  • 6,130
  • 2
  • 20
  • 27
1

$@ is an array of strings. You probably want to process the strings one at a time, not all together.

for i; do
    if [[ $i =~ ^-?[[:digit:]]+\.?[[:digit:]]*$ ]] || [[ $i =~ ^-?\.?[[:digit:]]+$ ]]; then
        echo yes - $i
    else
        echo no - $i
    fi
done
Waxrat
  • 2,075
  • 15
  • 13
0

In bash there is pattern matching with multiplier syntax that can help your problem. Here is a script to validate all arguments:

for ARG ; do
    [[ "$ARG" = +([0-9]) ]] && echo "$ARG is integer number" && continue
    [[ "$ARG" = +([0-9]).*([0-9]) ]] && echo "$ARG is float number" && continue
    [[ "$ARG" = *([0-9]).+([0-9]) ]] && echo "$ARG is float number" && continue
    [[ "$ARG" = -+([0-9]) ]] && echo "$ARG is negative integer number" && continue
    [[ "$ARG" = -+([0-9]).*([0-9]) ]] && echo "$ARG is negative float number" && continue
    [[ "$ARG" = -*([0-9]).+([0-9]) ]] && echo "$ARG is negative float number" && continue
    echo "$ARG is not a number."
done

The for loop automatically uses the arguments received by the script to load the variable ARG. Each test from the loop compares the value of the variable with a pattern [0-9] multiplied with + or * (+ is 1 or more , * is zero or more), sometimes there are multiple pattern next to each other. Here is an example usage with output:

$ ./script.sh 123 -123 1.23 -12.3 1. -12. .12 -.12 . -. 1a a1 a 12345.6789 11..11 11.11.11 
123 is integer number
-123 is negative integer number
1.23 is float number
-12.3 is negative float number
1. is float number
-12. is negative float number
.12 is float number
-.12 is negative float number
. is not a number.
-. is not a number.
1a is not a number.
a1 is not a number.
a is not a number.
12345.6789 is float number
11..11 is not a number.
11.11.11 is not a number.
czvtools
  • 591
  • 2
  • 7
0

I shall assume that you meant a decimal number, limited to either integers or floating numbers from countries that use a dot to mean decimal point. And such country does not use a grouping character (1,123,456.00125).

Not including: scientific (3e+4), hex (0x22), octal (\033 or 033), other bases (32#wer) nor arithmetic expressions (2+2, 9/7, 9**3, etc).

In that case, the number should use only digits, one (optional) sign and one (optional) dot.

This regex checks most of the above:

regex='^([+-]?)([0]*)(([1-9][0-9]*([.][0-9]+)?)|([.][0-9]+))$'

In words:

  • An optional sign (+ or -)
  • Followed by any amount of optional zeros.
  • Followed by either (…|…|…)

    1. A digit [1-9] followed by zero or more digits [0-9] (optionally) followed by a dot and digits.
    2. No digits followed by a dot followed by one or more digits.

Like this (since you tagged the question as bash):

regex='^([+-]?)([0]*)(([1-9][0-9]*([.][0-9]+)?)|([.][0-9]+))$'
[[ $n =~ $regex ]] || { echo "A $n is invalid" >&2; }

This will accept 0.0, and .0 as valid but not 0. nor 0.

Of course, that should be done in a loop, like this:

regex='^([+-]?)([0]*)(([1-9][0-9]*([.][0-9]+)?)|([.][0-9]+))$'

for    n
do     m=${n//[^0-9.+-]}       # Only keep digits, dots and sign.
       [[ $n != "$m" ]] && 
           { echo "Incorrect characters in $n." >&2; continue; }
       [[ $m =~ $regex ]] || 
           { echo "A $n is invalid" >&2; continue; }
       printf '%s\n' "${BASH_REMATCH[1]}${BASH_REMATCH[3]}"
done