bash, prompt for numerical input - bash

d is an internal server lookup tool I use.
I am looking to allow a user to input any number between 0 (or 1) and 9999 (let's call this userinput) and have it display the result of:
d $userinput (e.g. 1234)
Then manipulate the results of that lookup (below gets rid of everything but the IP address to ping later):
grep -E -o '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'`
I know I need to use the while true; do read $blah etc etc. I am just not familiar with read enough to format it properly and more importantly:
get it to prompt for a numerical input between 0-9999

The other answers have many flaws, because they check that the user didn't input a number outside of the range they want. But what if a user enters something that is not a number? their strategy is broken from the start.
Instead it's better to let go only when we're sure that the user entered a number which lies within the wanted range.
while :; do
read -ep 'Enter server number: ' number
[[ $number =~ ^[[:digit:]]+$ ]] || continue
(( ( (number=(10#$number)) <= 9999 ) && number >= 0 )) || continue
# Here I'm sure that number is a valid number in the range 0..9999
# So let's break the infinite loop!
break
done
The regex [[ $number =~ ^[[:digit:]]+$ ]] makes sure that the user only entered digits.
The clumsy (number=(10#$number)) part is here so that if the user enters a number that starts with a 0, bash would try to interpret it in radix 8 and we'd get a wrong result (e.g., if the user enters 010) and even an error in the case when a user enters, e.g., 09 (try it without this guard).
If you only want to prompt once and exit when the user inputs invalid terms, you have the logic:
read -ep 'Enter server number: ' number
[[ $number =~ ^[[:digit:]]+$ ]] || exit 1
(( ( (number=(10#$number)) <= 9999 ) && number >= 0 )) || exit 1
# Here I'm sure that number is a valid number in the range 0..9999
If you want to explain to the user why the script exited, you can use a die function as:
die() {
(($#)) && printf >&2 '%s\n' "$#"
exit 1
}
read -ep 'Enter server number: ' number
[[ $number =~ ^[[:digit:]]+$ ]] ||
die '*** Error: you should have entered a number'
(( ( (number=(10#$number)) <= 9999 ) && number >= 0 )) ||
die '*** Error, number not in range 0..9999'
# Here I'm sure that number is a valid number in the range 0..9999

<--edit-->
if all you want is the mechanic for prompting, try this:
echo -n "Enter server number:"
read userinput
then run validation checks on the input like this:
if [[ $userinput -lt 0 || $userinput -gt 9999 ]] # checks that the input is within the desired range
then
echo "Input outside acceptable range."
else
# insert your grep and ping stuff here
fi
<--end edit-->
on first read, i thought your problem sounded ideal for a wrapper script, so i was going to suggest this:
$ cat wrapper.sh
#!/usr/bin/bash
userinput=$1
if [[ $# != 1 ]] # checks that number of inputs is exactly one
then
echo "Too many inputs."
exit 2
elif [[ $userinput -lt 0 || $userinput -gt 9999 ]] # checks that the input is within the desired range
then
echo "Input outside acceptable range."
exit 3
fi
output=`d "$userinput"`
ping_address=`grep -E -o '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' <("$output")`
ping "$ping_address"
then call the script with like this:
$ wrapper.sh 1243

If you just want a number between two values, you can test their values:
read x
while [[ $x -lt 0 || $x -gt 9999 ]]; do
echo "bad value for x"
read x
done
echo "x=$x"

The read command doesn't prompt itself. Use a normal echo before to actually display a prompt. Use echo -n to not add a newline.

Related

Multiple conditions in while loop failing because of input type in Bash, or 3 digit number condition required

I am trying to control the user input to a script so that a only 3 digit number can pass. I started with two ifs and these work fine.
echo -e "Input sequence number:"
read SEQUENCE
NUMTEST='^[0-9]+$'
if ! [[ $SEQUENCE =~ $NUMTEST ]]; then
echo "ERROR:" "$SEQUENCE" "is not a number! Try again!"
exit 1
fi
SEQLEN=$(printf "%s" "$SEQUENCE" | wc -c)
if (($SEQLEN != 3)); then
echo "ERROR:" "$SEQUENCE" "is not a 3 digit number! Try again!"
exit 1
fi
A nicer solution would be to use a while loop so the user doesn't have to keep re-running the script, but whilst I can get a while loop to work for each individual condition, when I combine them a text input kills the loop as the numerical condition doesn't like it.
echo -e "Input sequence number:"
read SEQUENCE
NUMTEST='^[0-9]+$'
SEQLEN=$(printf "%s" "$SEQUENCE" | wc -c)
while ! [[ $SEQUENCE =~ $NUMTEST ]] && (($SEQLEN != 3)); do
echo "ERROR:" "$SEQUENCE" "is not a 3 digit number! Try again!"
echo -e "Input sequence number:"
read SEQUENCE
SEQLEN=$(printf "%s" "$SEQUENCE" | wc -c)
done
I'm thinking either I need to create a single condition $SEQUENCE == 3 digit number or find a way to suppress the errors such that any error mean the loop continues.
To be explicit my questions are:
Is there a way to write the condition $SEQUENCE == 3 digit number?
Can i suppress the error and still go to the loop, or is this a script terminating error?
Suggestions and improvements much appreciated. This is my first script so I won't be surprised if I'm committing some poor practices so any other recommendations appreciated.
Is there a way to write the condition $SEQUENCE == 3 digit number?
Yes. Change your regex to ^[0-9]{3}.
With a small trick you can also remove the duplicated input processing:
while true; do
read -p 'Input sequence number: ' sequence
[[ "$sequence" =~ ^[0-9]{3}$ ]] && break
echo "ERROR: '$sequence' is not a 3 digit number!"
done
Don't you mean:
while ! [[ $SEQUENCE =~ $NUMTEST ]] || (($SEQLEN != 3)); do
You need to loop again when not a number or not three digit (which is different from your construct, where you loop when not a number and length is not three).
Also, you can create a regexp for amtching three-digit numbers, as stated in #Socowi's answer.

check if bash script input variable is a letter

I want to write a short script to ssh to different servers depending on the argument I provide - if it is a number, I want the number to be a part of the server name, if it is a letter I want it connect to a certain server. So far I have this:
#!/bin/bash
if [ $1 -eq ^[1,3-5]$ ]; then
ssh -X servername1$1
elif [ $1=h ]; then
ssh -X servername2
fi
but it keeps complaining that integer expression is expected and always tries to connect to servername2, no matter whether I give it a number or a letter.
I tried quotation marks around the first and the second comparison, I tried double equality sign in the second comparison - all to no avail. How can I make this work?
[ $1 -eq ^[1,3-5]$ ] is not bash syntax.
You can write like that:
#!/bin/bash
if [[ $1 =~ ^[1,3-5]$ ]]; then
echo "$1 integer"
elif [ $1 = h ]; then
echo "$1 letter"
fi
Output:
[sahaquiel#sahaquiel-PC Stackoverflow]$ ./numorlet.sh 1
1 integer
[sahaquiel#sahaquiel-PC Stackoverflow]$ ./numorlet.sh 3
3 integer
[sahaquiel#sahaquiel-PC Stackoverflow]$ ./numorlet.sh 10
[sahaquiel#sahaquiel-PC Stackoverflow]$ ./numorlet.sh h
h letter
[sahaquiel#sahaquiel-PC Stackoverflow]$ ./numorlet.sh hhhh
[sahaquiel#sahaquiel-PC Stackoverflow]$
Also, tell me please, if the letter will be only 'h', or it can be any?
If any, use [[ instead of [ also in elif condition, with regex (I think ^[a-zA-Z]$ will work)
I suppose you have a server list where you are taking the argument $1 from.
If yes, you will have to do some changes in the code.
#!/bin/bash
if [[ $1 -eq ^[1,3-5]$ ]]; then
ssh -X servername1$1 < /dev/null
elif [[ $1==h ]]; then
ssh -X servername2 < /dev/null
fi
SSH takes input from stdin by adding input from /dev/null you are telling ssh to take input from that specific file.
Using [[...]] tells that if has an expression.
you can read more about the [[..]] in this link "Confused about operators"
Also $1=h is an assignment not a comparison; == is used for comparison.

How can I restrict bashs read command to only accept numeric values?

is there a way I can limit bashs read to only accept numeric input,
so when anything else then a number is added, the user gets promted again?
read -r -p "please enter 2 numbers: " number
Use a loop with a condition using a pattern:
#!/bin/bash
unset number
until [[ $number == +([0-9]) ]] ; do
read -r -p "please enter a number: " number
done
echo $((number + 1))
You might need to be more precise (#(0|#([1-9])*([0-9]))) if you want to use the number directly, because e.g. 09 will cause an error, as it will be interpreted as octal because of the starting 0.
change all none numeric value to empty and check for length.
if [[ -n ${number//[0-9]/} ]]; then
echo "please enter a numeric value!"
fi

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

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