Krazmad
Krazmad

Reputation: 35

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.

Upvotes: 2

Views: 893

Answers (2)

xdhmoore
xdhmoore

Reputation: 9886

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.

Upvotes: 3

glenn jackman
glenn jackman

Reputation: 246847

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.

Upvotes: 2

Related Questions