Shell Script - Case statement which finds exactly numbers (without +,-,/,*) - shell

Briefly,
I have a variable ($num) which contains random number(max.18), I need a case statement in shell (because along with checking number, I also have some alphabet conditions) which should validate the user input with the variable (must be less than $num).
Ex:
case $input in
...
1) ... ;;
2) ... ;;
...
so, here if I have only two conditions than I can write code like this, but my variable $num contains random number, how can I write case conditions which satisfies my below requirements.
If user inputs numbers like (1/3,3*1,3-2,2+1) it should not validate as a number
If user inputs numbers like (0001 or 01 or 000001) it should not validate as a number
The case condition should execute only if user inputs number between 1-$num no other number formats or symbols should not allowed.
Ex:
case $input in
[nN*]) ...
[aA*]) ...
...
*) if echo "$input" | egrep '^\-?[0-9]+$'; then
typeset -LZ num
num="$input"
if [ "$input" != "$num" ]; then
echo "$input not a valid number"
fi
else
echo "please choose proper choice option"
fi
;;
This code works but I want a normal case condition which should satisfy my requirements like if we have two or three options we can simply write the code but what if we have random options (which may decrease or increase) how to write a case condition in that case.
Thanks!

If the usage of a case is not compulsory, try and use some regex validation to have more control on what is allowed and what not:
[[ $input =~ ^[1-9][0-9]*$ ]]
# ^ ^ ^ ^
# beginning | | end
# | any digit
# a digit from 1 to 9
This checks that the data in $input contains a number that does not start with 0.
$ r=001
$ [[ $r =~ ^[1-9][0-9]*$ ]] && echo "yes"
$
$ r=1
$ [[ $r =~ ^[1-9][0-9]*$ ]] && echo "yes"
yes
$ r="3+1"
$ [[ $r =~ ^[1-9][0-9]*$ ]] && echo "yes"
$
You can then check if the number is lower than the stored one:
[ $r -le $num ]
All together:
$ num=15
$ r=5
$ [[ $r =~ ^[1-9][0-9]*$ ]] && [ $r -le $num ] && echo "yes"
yes
$ r=19
$ [[ $r =~ ^[1-9][0-9]*$ ]] && [ $r -le $num ] && echo "yes"
$
$ r="3+1"
$ [[ $r =~ ^[1-9][0-9]*$ ]] && [ $r -le $num ] && echo "yes"
$

read -p "enter number" yournumber
re='^[0-9]+$'
if ! [[ $yournumber =~ $re ]] ; then
echo "error: Not a number" >&2; exit 1
fi
This is only code rest of the things you need to do it.

Related

Checking if an integer is an element in array

I need to check if the value c exists in an integer array, I'm aware of how to approach this using for loops and if statements:
for c in {1..100};do
sequence=(2 4 6 8 10 12 14 16)
for value in "${sequence[#]}";do
if [[ $value -eq $c ]];then #If c exists in sequence
flag=1
break
fi
done
done
But I don't want this, I tried something similar to this:
[[ ${sequence[*]} =~ $c ]]
But it didn't give me the desired results, I think it works only with string arrays, not integers.
How could I approach this?
Converting my comment to answer so that solution is easy to find for future visitors.
You may use this grep + printf solution:
grep -qFx "$c" <(printf '%s\n' "${sequence[#]}") && echo "found" || echo "nope"
The problem with this method [[ ${sequence[*]} =~ $c ]] is that if $c is 1, than it'll match all instances with 1. Try this approach, make your sequence a regex like this
re=${sequence[*]}
re=${re// /|}
$ echo $re
2|4|6|8|10|12|14|16
Testing
c=1
$ [[ $c =~ $re ]] && echo ok || echo fail
fail
c=11
$ [[ $c =~ $re ]] && echo ok || echo fail
fail
c=12
$ [[ $c =~ $re ]] && echo ok || echo fail
ok

Regarding Bash substring comparison

I try to test if a string starts with a certain prefix. But my script seems not work (I would expect the "if" branch will not get run). Can some Bash expert help to take a look? thanks!
Here is my code and test result:
$ cat testb.bash
#!/bin/bash
my_var="abcdefg";
if [[ "${my_var:0:5}"=="order" ]]; then
echo "value of my_var is ${my_var}.";
fi;
if [[ "${my_var:0:5}" -eq "order" ]]; then
echo "value of my_var is ${my_var}.";
fi;
if [ "${my_var:0:5}"="order" ]; then
echo "value of my_var is ${my_var}.";
fi;
$ bash -x testb.bash
+ my_var=abcdefg
+ [[ -n abcde==order ]]
+ echo 'value of my_var is abcdefg.'
value of my_var is abcdefg.
+ [[ abcde -eq order ]]
+ echo 'value of my_var is abcdefg.'
value of my_var is abcdefg.
+ '[' abcde=order ']'
+ echo 'value of my_var is abcdefg.'
value of my_var is abcdefg.
$
Whitespace is significant in this case. As you can see in the -x output, it understands the first condition as
[[ -n "${my_var:0:5}==order" ]]
Moreover, to test for a prefix, you can use a pattern:
[[ $my_var == order* ]]
To test the existence of substring, you can use either of these:
if [[ "$j" =~ string1 ]]; then
if [[ $j == *string1* ]]; then
In your particular case, you miss a space surounding ==, so instead of
if [[ "${my_var:0:5}"=="order" ]]; then
it should be
if [[ "${my_var:0:5}" == "order" ]]; then
^ ^
Finally, note that your condition was evaluated as true because it was evaluating if [ "string" ], which is true if string is not empty:
$ [ "a" ] && echo "yes"
yes
Test
$ cat a
#!/bin/bash
my_var="abcdefg";
if [[ "${my_var:0:5}" == "order" ]]; then
echo "value of my_var is ${my_var}."
elif [[ "${my_var:0:5}" == "abcde" ]]; then
echo "yeahaa"
else
echo "is not"
fi
$ ./a
yeahaa
Ok, i tested your code, you shoud such as the following code:
prefix="pre_order";
pre="pre_"
len=${#pre}
echo $len
if [[ "${prefix:0:len}" == "blahvlah" ]] ; then
echo "dddd"
fi;
Notes:
use == for string comparation
for ${} you should initilize a string variable before ${}
use len=${#pre} for lenght of string.
A POSIX-compliant way to test for a prefix is to attempt to remove the prefix, and compare the result to the original string. If the two are the same, the prefix is not present, the removal fails, and the expression expands to the original string.
prefix=foo
string=foobar
if [ "${string#$prefix}" = "$string" ]; then
printf "$string does not start with $prefix\n"
else
printf "$string starts with $prefix\n"
fi

Checking if char is within set

I'm trying to check if some string from length 1 and has only following chars: [RGWBO].
I'm trying the following but it doesn't work, what am I missing?
if [[ !(${line[4]} =~ [RGWBO]) ]];
This is what you want:
if [[ ${line[4]} =~ ^[RGWBO]+$ ]];
This means that the string right from the start till the end must have [RGWBO] characters one or more times.
If you want to negate the expression just use ! in front of [[ ]]:
if ! [[ ${line[4]} =~ ^[RGWBO]+$ ]];
Or
if [[ ! ${line[4]} =~ ^[RGWBO]+$ ]];
This one would work with any usable version of Bash:
[[ -n ${LINE[0]} && ${LINE[0]} != *[^RGWB0]* ]]
Even though I prefer the simplicity of extended globs:
shopt -s extglob
[[ ${LINE[0]} == +([RGWBO]) ]]
Use expr (expression evaluator) to do substring matching.
#!/bin/bash
pattern='[R|G|W|B|O]'
string=line[4]
res=`expr match "$string" $pattern`
if [ "${res}" -eq "1" ]; then
echo 'match'
else
echo 'doesnt match'
fi
Approach
Test the string length with ${#myString}, if it's egal to 1 proceed to step 2 ;
Does is contains your pattern.
Code
re='[RGWBO]';
while read -r line; do
if (( ${#line} == 1 )) && [[ $line == $re ]]; then
echo "yes: $line"
else
echo "no: $line"
fi
done < test.txt
Resources
You may want to look at the following links:
Bash: Split string into character array's answer ;
Length of a string, use ${#myString} ;
Extracting parts of strings, use ${myString:0:8} ;
Data
The test.txt file contains this
RGWBO
RGWB
RGW
RG
R
G
W
B
O
V

How do I validate decimal numbers?

Hi I'm working on an assignment and got stuck on this part, how do I validate decimal numbers/numbers in shell?
It can accept numbers but not decimal numbers. I want it to be able to accept both.
This is what I have so far
if echo $value | egrep '^[0-9]+$' >/dev/null 2>&1 ; then
echo "OK"
else
echo "There Is An Error"
echo "Please Try Again"
fi
Instead of using grep, you can use the bash to check expression:
#!/bin/bash
value=98.23
if [[ "$value" =~ ^[0-9]+(\.[0-9]+)?$ ]]
then
echo good
else
echo bad
fi
use this regex instead ^[0-9]*(\.[0-9]+)?$
Using bash's pattern matching:
shopt -s extglob
while read line; do
if [[ $line == ?([-+])+([0-9])?(.*([0-9])) ]] ||
[[ $line == ?(?([-+])*([0-9])).+([0-9]) ]]
then
echo "$line is a number"
else
echo "$line NOT a number"
fi
done << END
1
-1
a
1a
1.0
1.
.0
.
-.0
+
+0
+.0
END
outputs
1 is a number
-1 is a number
a NOT a number
1a NOT a number
1.0 is a number
1. is a number
.0 is a number
. NOT a number
-.0 is a number
+ NOT a number
+0 is a number
+.0 is a number
The patterns:
optional sign, followed by one or more digits, followed optionally by a dot and zero or more digits
optional sign, followed by zero or more digits, followed by a mandatory dot, followed by one or more digits.
How about this:
if [ ! -z $(echo "$value" | grep -o "^[1-9][0-9]*\.\?[0-9]*$") ]; then echo ok; fi
-z tests for an empty string. So the negation [ ! -z "" ] will be fulfilled if the given string starts with a matching pattern.
In standard shell ([[ is non-standard) test will do the validation for you.
if test "$value" -eq 0 -o "$value" -ne 0 2> /dev/null; then
: # $value is an integer
else
: # $value is not an integer
fi
Try this: It checks negative and decimal number also.
echo $value | egrep '^-[0-9]+$|^[0-9]+$|^[0-9].[0-9]+$|^-[0-9].[0-9]+$' > /dev/null
Works in both Bash 3.0 and 4.0.
isInteger() {
[[ $1 =~ ^[0-9]+$ ]];
}
isDecimal() {
[[ $1 =~ ^[0-9]+\.[0-9]+$ ]] && ! isInteger $1;
}
computer:~ # isDecimal 123 && echo true || echo false
false
computer:~ # isDecimal 12.34 && echo true || echo false
true
computer:~ # isDecimal 12.34a && echo true || echo false
false
computer:~ # isDecimal 0.0000001 && echo true || echo false
true
To check if number, simply test against both functions.

Bash - why the "0" is not recognized as a number?

I found an interesting Bash script that will test if a variable is numeric/integer. I like it, but I do not understand why the "0" is not recognized as a number? I can not ask the author, hi/shi is an anonymous.
#!/bin/bash
n="$1"
echo "Test numeric '$n' "
if ((n)) 2>/dev/null; then
n=$((n))
echo "Yes: $n"
else
echo "No: $n"
fi
Thank you!
UPDATE - Apr 27, 2012.
This is my final code (short version):
#!/bin/bash
ANSWER=0
DEFAULT=5
INDEX=86
read -p 'Not choosing / Wrong typing is equivalent to default (#5): ' ANSWER;
shopt -s extglob
if [[ $ANSWER == ?(-)+([0-9]) ]]
then ANSWER=$((ANSWER));
else ANSWER=$DEFAULT;
fi
if [ $ANSWER -lt 1 ] || [ $ANSWER -gt $INDEX ]
then ANSWER=$DEFAULT;
fi
It doesn't test if it is a numeric/integer. It tests if n evaluates to true or false, if 0 it is false, else (numeric or other character string) it is true.
use pattern matching to test:
if [[ $n == *[^0-9]* ]]; then echo "not numeric"; else echo numeric; fi
That won't match a negative integer though, and it will falsely match an empty string as numeric. For a more precise pattern, enable the shell's extended globbing:
shopt -s extglob
if [[ $n == ?(-)+([0-9]) ]]; then echo numeric; else echo "not numeric"; fi
And to match a fractional number
[[ $n == #(?(-)+([0-9])?(.*(0-9))|?(-)*([0-9]).+([0-9])) ]]

Resources