smallest value issue in shell script - if-statement

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 ]

Related

Negative to positive number range in regex in bash scripting [duplicate]

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

How to test if variable is a number with certain max digits in linux non-bash shell script

Here I found nice solution to test if variable is a number:
case $string in
''|*[!0-9]*) echo bad ;;
*) echo good ;;
esac
I'm trying to modify it to test if variable is a 1,2,3 or 4-digit natural number (within a range 0-9999), so I added |*[0-9]{5,}* to exclude numbers with more than 5 digits, but the code below prints good for numbers greater then 9999 e.g. 1234567.
case $string in
''|*[!0-9]*|*[0-9]{5,}*) echo bad ;;
*) echo good ;;
esac
I'm using ash from busybox.
You can use numeric testing:
$ s=123
$ (( s>=0 && s<=9999 )) && echo "true" || echo "false"
true
$ s=123456
$ (( s>=0 && s<=9999 )) && echo "true" || echo "false"
false
$ s=-1
$ (( s>=0 && s<=9999 )) && echo "true" || echo "false"
false
You just need to make sure that the string is all digits with optional ± at the start. A non numeric string used in numeric context will evaluate to 0 so you need to test for that.
Which you can use your case statement for:
case $s in
''|*[!0-9]*) echo bad ;;
*) (( s>=0 && s<=9999 )) && echo "true" || echo "false" ;;
esac
Or:
$ [ "$s" -eq "$s" ] 2>/dev/null && (( s>=0 && s<=9999 )) && echo "true" || echo "false"
Works too.
These should work under any POSIX shell.
Not sure if you need this in a case statement, since I would just do:
if { test "$string" -ge 0 && test "$string" -lt 10000; } 2> /dev/null; then
echo good
else
echo bad
fi
If you want to use a case statement in a strictly portable shell, you're probably stuck with:
case $string in
[0-9]|[0-9][0-9]|[0-9][0-9][0-9]|[0-9][0-9][0-9][0-9]) echo good;;
*) echo bad;;
esac
case $([[ $num =~ ^[0-9]+$ ]] && echo 0) in
0) echo good ;;
*) echo bad ;;
esac

BASH: testing that arguments are a list of numbers

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

What's the error with this BASH regex script?

I'm trying to make a program that reads in n strings and checks them for pertaining to a regex pattern: XXXXX1234X where X is an uppercase character and {1,2,3,4} is any digit. As far as I checked, the regex pattern is correct. The problem seems to be in the input and comparison of strings.
read n
i=0
declare -a str
while [ $i -lt $n ]
do
read 'str[$i]'
i=$((i+1))
done
i=0
while [ $i -lt $n ]
do
[[ $(str[$i]) =~ ^([A-Z]){5}([0-9]){4}([A-Z]){1}$ ]] && echo YES || echo NO
i=$((i+1))
done
I did a minor modification to your code, I replaced the ( and ) with { } in the regex test:
[[ ${str[$i]} =~ ^...
Ran some test and it worked:
#!/bin/bash
read n
i=0
declare -a str
while [ $i -lt $n ]
do
read 'str[$i]'
i=$((i+1))
done
i=0
while [ $i -lt $n ]
do
[[ ${str[$i]} =~ ^([A-Z]){5}([0-9]){4}([A-Z]){1}$ ]] && echo YES || echo NO
i=$((i+1))
done

How should I use exact keyword matching as a condition in the case statement?

I was trying to write myself some handy scripts in order to legitimately slacking off work more efficiently, and this question suddenly popped up:
Given a very long string $LONGEST_EVER_STRING and several keywords strings like $A='foo bar' , $B='omg bbq' and $C='stack overflow'
How should I use exact keyword matching as a condition in the case statement?
for word in $LONGEST_EVER_STRING; do
case $word in
any exact match in $A) do something ;;
any exact match in $B) do something ;;
any exact match in $C) do something ;;
*) do something else;;
esac
done
I know I can write in this way but it looks really ugly:
for word in $LONGEST_EVER_STRING; do
if [[ -n $(echo $A | fgrep -w $word) ]]; then
do something;
elif [[ -n $(echo $B | fgrep -w $word) ]]; then
do something;
elif [[ -n $(echo $C | fgrep -w $word) ]]; then
do something;
else
do something else;
fi
done
Does anyone have an elegant solution? Many thanks!
You could use a function to do a little transform in your A, B, C variables and then:
shopt -s extglob
Ax="+(foo|bar)"
Bx="+(omg|bbq)"
Cx="+(stack|overflow)"
for word in $LONGEST_EVER_STRING; do
case $word in
$Ax) do something ;;
$Bx) do something ;;
$Cx) do something ;;
*) do something else;;
esac
done
I would just define a function for this. It'll be slower than grep for large wordlists, but faster than starting up grep many times.
##
# Success if the first arg is one of the later args.
has() {
[[ $1 = $2 ]] || {
[[ $3 ]] && has "$1" "${#:3}"
}
}
$ has a b c && echo t || echo f
f
$ has a b c a d e f && echo t || echo f
t
A variation on /etc/bashrc's "pathmunge"
for word in $LONGEST_EVER_STRING; do
found_word=false
for list in " $A " " $B " " $C "; do
if [[ $list == *" $word "* ]]; then
found_word=true
stuff with $list and $word
break
fi
done
$found_word || stuff when not found
done