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

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

Related

Is there any way to check if there is any input then contiue?

I am a newbie in bash programming. I need a bash script which can check if there is an input or not.If there is an input then continue to the second question, otherwise it will not continue unless it forces me to write the input (data). I wrote this script but is not working:
echo "Write buss no:"
read bussno
while [ true ] ; do
if [ -z $bussno ] ; then
echo "\Buss No. should be filled"
read bussno
else
echo "Write from date: "
read startdate
if [ -z $startdate ] ; then
echo "\start date should be filled"
read startdate
fi
done
Bash read command support timeout. You can set the timeout to zero:
From man bash:
-t timeout
Cause read to time out and return failure if a complete line of input
is not read within timeout seconds. timeout may be a decimal number
with a fractional portion following the decimal point. This option is
only effective if read is reading input from a terminal, pipe, or
other special file; it has no effect when reading from regular files.
If timeout is 0, read returns success if input is available on the
specified file descriptor, failure otherwise. The exit status is
greater than 128 if the timeout is exceeded.
Basically use bussno= ; read -t0 bussno instead of read bussno
There are ways, but you want to keep it simple, yes?
Maybe the logic you want is something like
while [[ -z "$bussno" ]]; do read -r -p "Please enter a business number: " bussno; done
while [[ -z "$startdate" ]]; do read -r -p "Please enter a start date: " startdate; done
This still leaves a lot to be desired in terms of data confirmation, though.
You can add a regex for that if you want, and some follow up confirmations.
For example, for the date,
valid_date='^[[:digit:]]{8}$'
msg="A valid start date is today or later as YYYYMMDD, e.g.: 20220823"
echo "$msg"
until [[ -n "$startdate" && $startdate =~ $valid_date ]]; do
read -p "Please enter a valid start date: " startdate
if date -d "$startdate" >/dev/null 2>&1 && # fail if invalid date
(( "$(date +'%Y%m%d')" <= "$startdate" )) # fail if in past
then break
else echo "'$startdate' is not a valid date."
echo "$msg"
startdate= # reset
fi
done
These still leave much to be desired, but it's a start.

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

bash, prompt for numerical input

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.

Allow read to only accept two lengths

I am trying to add to a script I'm writing a restriction to the input where the user can only input 9 or 10 characters I set it up like this but when I run it, it doesn't work the way I want it to. Everything I type in comes back as 10 characters even if I just put one number. What is wrong with my code?
#!/bin/bash
#
echo "Please input numbers only"
read inputline
if read -n10 inputline
then
echo "10 chars"
else
if read -n9 inputline
then
echo "9 chars"
else
echo "invalid input length"
fi
Your script is asking for input three times. I'm assuming that the following is closer to what you intend:
#!/bin/bash
read -p "Please input numbers only " inputline
len=${#inputline}
if (( len == 9 || len == 10 ))
then
echo "$len chars"
else
echo "invalid input length"
fi
The -n option to read limits the input to the specified number of characters but accepts that length without pressing enter. You can enter fewer by pressing Enter though. I've found it to be useful for -n 1 but rarely otherwise.
Use the offset Parameter Expansion to truncate whatever they enter to a max of 10 chars like so:
$ num="123456789012"; echo ${num::10}
1234567890

Resources