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 "..#"
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
$# 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
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.
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 (…|…|…)
A digit [1-9] followed by zero or more digits [0-9] (optionally) followed by a dot and digits.
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
Related
I'm trying to find most negative value that are stored in array.
I used the following code
small=99999.999
for M in ${val[#]}
do
if [ "$M" \< "$small" ]
then small="$M"
fi
done
Let's consider the values are
0
0.5764
1.2934
0.8826
3.3143
2.8783
4.5771
0.549
2.4977
0.2294
1.0407
-0.0854
0.1819
1.911
0.5448
1.1276
0.2128
1.5406
-0.2361
-0.7184
-0.0082
The above code is producing smallest value as -0.0082 which is not the answer.
Can anybody pl let me know the fact?
You are comparing strings (lexicographical order), instead you can put your condition in double parenthesis (to perform a numeric comparison) if((a < b)); then or use -lt notation:
#!/bin/bash
for M in ${val[#]}
do
if [ "$M" -lt "$small" ]
then small="$M"
fi
done
Try it out in bash:
if \[ 100 \< 2 \]; then echo "yes"; else echo "no"; fi
it will output yes, but its false.
if ((100 < 2)); then echo "yes"; else echo "no"; fi
it will output no, which is true.
if \[ 100 -lt 2 \]; then echo "yes"; else echo "no"; fi
it will also give you the correct answer.
But bash only handles integer math, so you can use bc in your condition:
if [ $(bc <<< "$M < $small") -eq 1 ]
I want to just insert number between two values, and otherwise the script repeated until correct number.
This is my script and it does not work correctly:
validation(){
read number
if [ $number -ge 2 && $number -ls 5 ]; then
echo "valid number"
break
else
echo "not valid number, try again"
fi
}
echo "insert number"
validation
echo "your number is" $number
If you are using Bash, you are better off using the arithmetic expression, ((...)) for readability and flexibility:
if ((number >= 2 && number <= 5)); then
# your code
fi
To read in a loop until a valid number is entered:
#!/bin/bash
while :; do
read -p "Enter a number between 2 and 5: " number
[[ $number =~ ^[0-9]+$ ]] || { echo "Enter a valid number"; continue; }
if ((number >= 2 && number <= 5)); then
echo "valid number"
break
else
echo "number out of range, try again"
fi
done
((number >= 2 && number <= 5)) can also be written as ((2 <= number <= 5)).
See also:
Test whether string is a valid integer
How to use double or single brackets, parentheses, curly braces
Your if statement:
if [ $number -ge 2 && $number -ls 5 ]; then
should be:
if [ "$number" -ge 2 ] && [ "$number" -le 5 ]; then
Changes made:
Quoting variables is considered good practice.
ls is not a valid comparison operator, use le.
Separate single-bracket conditional expressions with &&.
Also you need a shebang in the first line of your script: #!/usr/bin/env bash
if [ $number -ge 2 && $number -ls 5 ]; then
should be
if [[ $number -ge 2 && $number -le 5 ]]; then
see help [[ for details
Try bellow code
echo "Enter number"
read input
if [[ $input ]] && [ $input -eq $input 2>/dev/null ]
then
if ((input >= 1 && input <= 4)); then
echo "Access Granted..."
break
else
echo "Wrong code"
fi
else
echo "$input is not an integer or not defined"
fi
2 changes needed.
Suggested by Sergio.
if [ "$number" -ge 2 ] && [ "$number" -le 5 ]; then
There is no need of break. only meaningful in a for, while, or until loop
while :; do
read option
if [[ $option -ge 1 && $option -lt 4 ]]; then
echo "correct"
c
break
else
echo "Incorrect option selected,choose an option between [1-4]"
fi
done
Need to have a read statement in BASH that will ask user to input 2 digits on the same line with a space in between. Check if both are digits and there is a space in between.
#!/bin/bash
while :
do
read -p "digits a b?: " resp
if [ -z "${resp}" ] ; then continue;fi
respCtrl=`echo $resp | sed 's/[^0-9 ]//g'`
if [ ! ${#respCtrl} -eq ${#resp} ] ; then continue ;fi
set ${resp}
digit1=$1
digit2=$2
if [ "${digit1} ${digit2}" != "${resp}" ] ; then continue;fi
echo ${digit1} ${digit2}
break
done
#!/bin/bash
result=""
while [[ $result == *" "* || $result == "" ]];do
read -p "Type 2 digits without space:" result;
echo "Error";
done
if [[ " " =~ ^[0-9]*$ ]]; then echo "si"; else echo "no"; fi; //Echoes No
if [[ "" =~ ^[0-9]*$ ]]; then echo "si"; else echo "no"; fi; //Echoes Yes
Is this a bug or am I missing something?
This is as expected. You specified 0 or more times (*) a digit ([0-9]). An empty string is 0 times that.
Use a + (which means "1 or more times") instead of a *:
if [[ " " =~ ^[0-9]+$ ]]; then echo "si"; else echo "no"; fi; // Should echo No
if [[ "" =~ ^[0-9]+$ ]]; then echo "si"; else echo "no"; fi; // Should echo No
The first one is a space, which does not match the [0-9]* regex.
The second is empty, which is [0-9]* because * also implies 0 ocurrencies. If you make it match at least one ocurrency with +, then it is false:
$ if [[ " " =~ ^[0-9]+$ ]]; then echo "si"; else echo "no"; fi;
no
* in a regex means "0 or more", so with nothing in the target string, the regex trivially matches.
[0-9]* matches zero or more digits, so yes, it matches the empty string. If you don't want to match the empty string, use [0-9]+, which matches one or more digits.
I need to ensure that the last character in a string is a /
x="test.com/"
if [[ $x =~ //$/ ]] ; then
x=$x"extention"
else
x=$x"/extention"
fi
at the moment, false always fires.
Like this, for example:
$ x="test.com/"
$ [[ "$x" == */ ]] && echo "yes"
yes
$ x="test.com"
$ [[ "$x" == */ ]] && echo "yes"
$
$ x="test.c/om"
$ [[ "$x" == */ ]] && echo "yes"
$
$ x="test.c/om/"
$ [[ "$x" == */ ]] && echo "yes"
yes
$ x="test.c//om/"
$ [[ "$x" == */ ]] && echo "yes"
yes
You can index strings in Bash using ${var:index} and ${#var} to get the length of the string. Negative indices means the moving from the end to the start of the string so that -1 is index of the last character:
if [[ "${x:${#x}-1}" == "/" ]]; then
# last character of x is /
fi
Your condition was slightly incorrect. When using =~, the rhs is considered a pattern, so you'd say pattern and not /pattern/.
You'd have got expected results if you said
if [[ $x =~ /$ ]] ; then
instead of
if [[ $x =~ //$/ ]] ; then
You can do this generically using bash substrings $(string:offset:length} - length is optional
#x is the length of x
Therefore
$n = 1 # 1 character
last_char = ${x:${#x} - $n}
For future references,
$ man bash
has all the magic
${parameter:offset:length}
Substring Expansion. Expands to up to length characters of parameter
starting at the character specified by offset. If length is
omitted, expands to the substring of parameter starting at the
character specified by offset. length and offset are arithmetic
expressions ...