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 "..#"

2
  • Assuming that the syntax was correct (it isn't), why wouldn't something equal itself? Commented Dec 18, 2016 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. I was trying to get it to work on an infinite number of parameters. Commented Dec 18, 2016 at 17:21

4 Answers 4

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
Sign up to request clarification or add additional context in comments.

Comments

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

Comments

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.

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.