unix shell programming - checking for numbers and letters - bash

I'm fairly new to Unix shell or bash programming.
I'm doing a project on an inventory program. I'm wondering if it is possible to check for numbers and letters within a function.
For example, I were to sell a hamburger, within the price I would only put numbers, if I were to input letters in it, how can i check that I have put letters instead of numbers in it?
Sorry if my English is bad, English is not my first language.
echo -n "Food :"
read food
echo -n "Price :"
read price

You could do it in multiple ways.
Using test with if condition:
if [ $var -eq $var 2> /dev/null ]
then
...
fi
OR
if [[ $var == +([0-9]) ]]
then
## its a number
fi
Using egrep with regex command like:
if [[ echo $var | egrep -q '^[0-9]+$' ]]
then
####...its a number...###
fi

It would be like:
read price
expression='^[0-9]+$'
if ! [[ $price =~ $expression ]] ; then
echo "Error: Please enter a number" >&2; exit 1
fi
Here, ^[0-9]+$ shows "starting(^) with a number[0-9] and continuing the same till the end($)
Hope that helps.

Allowing prices to have decimal points, this checks for valid prices:
re='^[0-9]+\.?[0-9]*$'
[[ $price =~ $re ]] && echo "Is Valid"
A more complete working example, which provides and error message for bad numbers, is:
read -p "Enter the food name: " food
re='^[0-9]+\.?[0-9]*$'
while true
do
read -p "Enter the price: " price
[[ $price =~ $re ]] && break
echo " You must enter a valid number"
echo " Please try again"
done
echo ""
echo "The price of $food is $price"
The following shows the above in action:
$ bash script.sh
Enter the food name: Hamburger
Enter the price: a lot
You must enter a valid number
Please try again
Enter the price: 1.9y
You must enter a valid number
Please try again
Enter the price: 0.99
The price of Hamburger is 0.99
How it works
The regular expression is ^[0-9]+\.?[0-9]*$. Let's look at it one piece at a time:
^ matches the beginning. This assures that no non-number characters precede the number
[0-9]+ matches one or more numbers
\.? matches the decimal point, if there is one.
[0-9]* matches numbers, if any, following the decimal point.
$ matches at the end of the string, assuring that there are no non-number characters following the number.

Related

Shell Scripting - Numeric Checks and if statement questions

I'm relatively new here and to the coding world. I'm currently taking a class in Shell Scripting and I'm a bit stuck.
I'm trying to do a little extra credit and get the script to check for command line arguments and if none or only 1 is given, prompt the user to input the missing values.
For the most part I've been able to get most of it to work except for when it comes to the numeric check part. I'm not completely sure that I am doing the nested if statements correctly because it's displaying both the "if" echo and the "else" echo.
My script so far:
q=y
# Begins loop
until [[ $q == n ]];do
# Checks command line arguments
if [[ $# -lt 2 ]];then
# Asks for second number if only 1 argument.
if [[ $# == 1 ]];then
read -r -p "Please enter your second number: " y
if [[ y =~ [1-9] ]];then
echo "You've chosen $1 as your first number and $y as your second number."
break
else
echo "This is not a valid value, please try again."
fi
# Asks for both numbers if no arguments.
else
read -r -p "Please enter your first number: " x
if [[ x =~ [1-9] ]];then
break
else
echo "This is not a valid value, please try again."
fi
read -r -p "Please enter your second number: " y
if [[ y =~ [1-9] ]];then
break
else
echo "This is not a valid value, please try again."
fi
echo "You've chosen $x as your first number and $y as your second number."
fi
# If both command line arguments are provided, echo's arguments, and sets arguments as x and y values.
else
echo "You've chosen $1 as your first number and $2 as your second number."
x=$1
y=$2
fi
read -r -p "Would you like to try again? (n to exit): " q
done
When I run it I get this for output:
Please enter your first number: 1
This is not a valid value, please try again.
Please enter your second number: 2
This is not a valid value, please try again.
You've chosen 1 as your first number and 2 as your second number.
Please enter your first number:
And will just continue to loop without breaking. Any help/guidance would be greatly appreciated, thank you.
In your expression:
if [[ x =~ [1-9] ]]; then
You are actually comparing the string literal "x" with the regex. What you want is the variable:
if [[ $x =~ [1-9] ]]; then
This will interpolate the variable first in order to compare the variable's value with the regex. I think this change also applies to some of the other comparison expressions in your code.
However, as glenn jackman and user1934428 have commented, this will also match things like foo1bar, which is probably not what you want. To fix this, you can add start/end matchers to your regex. Finally, you may want to match even if the input has leading or trailing spaces. One way to do this is to add some [[:space:]]*'s to match zero or more spaces around your [1-9]:
if [[ $x =~ ^[[:space:]]*[1-9][[:space:]]*$ ]]; then
So, to break down the regex:
^ start of input
[[:space:]]* zero or more whitespaces
[1-9] a single digit, 1-9
[[:space:]]* zero or more whitespaces
$ end of the input
I'm assuming from your question than you only want to match on a single digit, not, for example, 12, or the digit 0. To match those would require a couple more regex tweaks.
and...glob pattern
Just because glen jackman's answer led me down a bash man page adventure 🏄 and I wanted to try them out, this is a glob pattern version (note the == instead of =~):
if [[ $x == *([[:space:]])[1-9]*([[:space:]]) ]]; then
It's basically the same pattern. But notably, glob patterns seem to be implicitly anchored to the start/end of the string being matched (they are tested against the entire string) so they don't need the ^ or $, while regular expressions match against substrings by default, so they do need those additions to avoid foo1bar matching. Anyway, probably more than you cared to know.
Here's an alternate implementation, for your consideration: hit me up with any questions
#!/usr/bin/env bash
get_number() {
local n
while true; do
read -rp "Enter a number between 1 and 9: " n
if [[ $n == [1-9] ]]; then
echo "$n"
return
fi
done
}
case $# in
0) first=$(get_number)
second=$(get_number)
;;
1) first=$1
second=$(get_number)
;;
*) first=$1
second=$2
;;
esac
# or, more compact but harder to grok
[[ -z ${first:=$1} ]] && first=$(get_number)
[[ -z ${second:=$2} ]] && second=$(get_number)
echo "You've chosen $first as your first number and $second as your second number."
This uses:
a function to get a a number from the user, so you don't have so much duplicated code,
a case statement to switch over the $# variable
input validation with the == operator within [[...]] -- this operator is a pattern matching operator, not string equality (unless the right-hand operand is quoted)
Note that [[ $x =~ [1-9] ]] means: "$x contains a character in the range 1 to 9" -- it does not mean that the variable is a single digit. If x=foo1bar, then the regex test passes.

bash - how to put $RANDOM into value?

newbie to bash:
basically I want to compare the result of $RANDOM to another value which is given by the user through 'read'
code for more info:
echo $RANDOM % 10 + 1 | bc
basically I want an if statement as well to see if the result of that $RANDOM value is equal to something that the user typed in e.g.:
if [ [$RANDOM VALUE] is same as $readinput
#readinput is the thing that was typed before
then
echo "well done you guessed it"
fi
something along the lines of that!!
to summarise
how do i make it so that i can compare a read input value to echo "$RANDOM % 10 + 1 | bc"
think of the program I am making as 'GUESS THE NUMBER!'
all help VERY MUCH APPRECIATED :)
There's no need for bc here -- since you're dealing in integers, native math will do.
printf 'Guess a number: '; read readinput
target=$(( (RANDOM % 10) + 1 )) ## or, less efficiently, target=$(bc <<<"$RANDOM % 10 + 1")
if [ "$readinput" = "$target" ]; then
echo "You correctly guessed $target"
else
echo "Sorry -- you guessed $readinput, but the real value is $target"
fi
The important thing, though, is the test command -- also named [.
test "$readinput" = "$target"
...is exactly the same as...
[ "$readinput" = "$target" ]
...which does the work of comparing two values and exiting with an exit status of 0 (which if will treat as true) should they match, or a nonzero exit status (which if will treat as false) otherwise.
The short answer is to use command substitution to store your randomly generated value, then ask the user for a guess, then compare the two. Here's a very simple example:
#/bin/bash
#Store the random number for comparison later using command substitution IE: $(command) or `command`
random=$(echo "$RANDOM % 10 + 1" | bc)
#Ask the user for their guess and store in variable user_guess
read -r -p "Enter your guess: " user_guess
#Compare the two numbers
if [ "$random" -eq "$user_guess" ]; then
echo "well done you guessed it"
else
echo "sorry, try again"
fi
Perhaps a more robust guessing program would be embedded in a loop so that it would keep asking the user until they got the correct answer. Also you should probably check that the user entered a whole number.
#!/bin/bash
keep_guessing=1
while [ "$keep_guessing" -eq 1 ]; do
#Ask the user for their guess and check that it is a whole number, if not start the loop over.
read -r -p "Enter your guess: " user_guess
[[ ! $user_guess =~ ^[0-9]+$ ]] && { echo "Please enter a number"; continue; }
#Store the random number for comparison later
random=$(echo "$RANDOM % 10 + 1" | bc)
#Compare the two numbers
if [ "$random" -eq "$user_guess" ]; then
echo "well done you guessed it"
keep_guessing=0
else
echo "sorry, try again"
fi
done

How do I check if a variable contains at least one alphabetic character in Bash?

The version of bash i use is 4.3.11 and I use 'mcedit' as my script writer. I want to check if a variable contains at least one alphabetical character such that 'harry33' and 'a1111' are deemed valid.
I've tried the code below in my script however an error is returned which states that there is an error with '[[:'
SOLVED
#name = "test123"
read -p "Enter you name: " name
until [[ "$name" =~ [A-Za-z] ]]; do
read -p "Please enter a valid name: " name
done
The code you wrote has a couple of issues with spaces (one you already corrected after the [[) and the spaces around an equal should not exist:
name="test123"
if [[ "$name" =~ [A-Za-z] ]]; then
echo "Please enter valid input: "
fi
The line: "Please enter valid input: " will be printed in this case.
As $name contains several values in the range a-z.
Maybe what you want is the opposite, that the line is printed if the variable contains characters outside the range:
name="test"
if [[ "$name" =~ [^A-Za-z] ]]; then
echo "The input contains characters outside the a-z or A-Z range."
fi
But in this case, the characters accepted may include accented (international) characters like é, è, or ë. Which are in-range in several Language Collate sequences.
That also happens with [^[:alpha:]].
Either you embrace full internationalization or limit yourself to old ASCII:
name="test"
LC_COLLATE=C
if [[ "$name" =~ [^A-Za-z] ]]; then
echo "The input contains characters outside the a-z or A-Z range."
fi
If you want to have as valid names with Alpha and digits, there are two options. One which is very strict (old ASCII ranges):
name="harry33"
LC_COLLATE=C
if [[ "$name" =~ ^[0-9A-Za-z]+$ ]]; then
echo "The input only contains digits and alpha."
fi
The other option will also allow é, ß or đ, etc. (which is perfectly fine for an internationalized name), and the range is defined either by the variable LC_COLLATE or LC_ALL as set in the environment.
name="harry33"
if [[ "$name" =~ ^[[:alnum:]]+$ ]]; then
echo "The input only contains digits and alpha."
fi
This option will reject $%&() and similar.
The portable solution free of bashisms such as [[ would be
case $name in
(*[a-zA-Z]*)
echo "Yay! Got an alphabetic character."
;;
(*)
echo "Hmm, no a-z or A-Z found."
;;
esac
First, Bash is picky about spacing, so have a space after your test brackets [[ and ]] Also, if you are looking for user names, I'd think you'd want it to start with a letter, and if it didn't, then echo your prompt.
if [[ ! $var =~ ^[[:alpha:]] ]]; then
echo -n "Please enter valid input: "
read response
fi

Bash if doesn't match the content

My code:
#!/bin/bash
content="My sms content"
nomer="My Phone number"
if [[ -n "$content" && -n "$nomer" ]]; then
echo "it passes the first filter..."
if [ "$(echo $content| awk '{print tolower($0)}')" = "My sms content" ]; then
echo "Yes it matches the content"
else
echo "the variable doesn't match the content"
fi
fi
It displays nothing when I run the code. What I want is that it displays Yes it matches the content How do I do that?
UPDATE as #BlueMoon answer I updated my code but it still says the variable doesn't match the content
I want it to match the content
As well as the problem of this passing only if both strings are empty:
if [[ -z "$content" && -z "$nomer" ]]
(you probably meant -n which is the non-empty variant), you also have the problem that a string converted to lower-case will never contain a capital M:
if [ "$(echo $content| awk '{print tolower($0)}')" = "My sms content" ]
(you probably meant "my sms content").
In any case, if you're using a relatively modern version of bash, it has case conversion built in so you don't have to use external processes. The following transcript shows the four possibilities, upper-case first, upper-case all, lower-case first and lower-case all:
pax> word='abcde' ; echo ${word^}
Abcde
pax> word='abcde' ; echo ${word^^}
ABCDE
pax> word='ABCDE' ; echo ${word,}
aBCDE
pax> word='ABCDE' ; echo ${word,,}
abcde
I gather the rationale here was that the caret ^ points up while the comma , points down. So you can simply use:
if [[ "${content,,}" == "my sms content" ]]
-z tests for being zero length. So what you're comparing is to check that both strings are empty.
This line
if [[ -z "$content" && -z "$nomer" ]]; then
says if both strings are of non-zero then do it.
What you probably meant is if both are non-empty:
if [[ -n "$content" && -n "$nomer" ]]; then
From manual:
-z string
True if the length of string is zero.
string
-n string
True if the length of string is non-zero.

BASH if comparison to force a valid e-mail address format is entered

I have a useradd bash script which requests the user enter an e-mail address for the user being created. This is so the user receives his username/password in an e-mail when his/her account is created.
Currently this part of the code is very simple:
echo Enter the users e-mail address
read ADDRESS
What i'm finding is that sometimes when the operators run the script they are entereing blank information. How can I put a if statement in place that enforces they enter an e-mail address format.
I tried the following code but it doesn't work. The idea was to at least verify they are using the # symbol.
if [[ $string != "#" ]] ; then
echo You have entered an invalid e-mail address!
exit 1
else
# do something
fi
If you're just looking for something quick and dirty, this bash conditional expression will match something that has at least one char, an '#', at least one char, a dot, and at least one char.
[[ "$email" == ?*#?*.?* ]]
Examples
$ [[ "a#b.c" == ?*#?*.?* ]] && echo Y || echo n
Y
$ [[ "foo#bar" == ?*#?*.?* ]] && echo Y || echo n
n
Actual email validation is gnarly (see here)
!= tests for exact inequality: the string would have to be exactly # with nothing else. Two ways to do the test you want are
case "$string" in
*#*)
;;
*)
echo You have entered an invalid email address! >&2
exit 1
;;
esac
or
if ! expr "$string" : '.*#' >/dev/null; then
echo You have entered an invalid email address! >&2
exit 1
fi
You need to redirect the result from expr because it will print the matched length. Note also that case uses shell globs, whereas expr uses POSIX basic regular expressions (so you can't use +, ?, etc.); and you need to quote the regex passed to expr so the shell doesn't expand it, but for case the whole point is to have the shell expand it.
I generally prefer the case one unless I actually need a regex.
You could e.g. use bash's =~ operator, e.g.:
if [[ $string =~ "#" ]] ; then
# do something
else
echo You have entered an invalid e-mail address!
exit 1
fi
You can use glob-style patterns in if conditionals in bash:
if [[ $string != *"#"* ]] ; then
echo You have entered an invalid e-mail address!
exit 1
else
# do something
fi
I'd go a step further and require at least one character at either side of the #:
if [[ $string != *?"#"?* ]] ; then
echo You have entered an invalid e-mail address!
exit 1
else
: # do something
fi

Resources