Eyal Gerber
Eyal Gerber

Reputation: 1637

How to call a function in bash while passing an array as an argument AND having the function return a value?

I am trying to create a simple bash script that tells the user to enter a char. The user only has a limited number of possible options to enter. If he enters the correct one out of the options allotted, then that option is then returned back from the function. If not, then the user is asked to enter a correct option.

Here is the script I wrote:

#receive from the user an input and verify that it is a valid option. If not, try again. 
#input1: The number of valid options
#input2: array of valid inputs (can be single characters or strings).
# EXAMPLE:
# ARRAY=("a" "b")
# ReceiveValidInput 2 "${ARRAY[@]}" # there are TWO valid options. Valid options are ${ARR[0]}="a", ${ARR[1]}="b"
function ReceiveValidInput()
{
    echo "testing"
    InputNum=$1 #get the first input1
    shift       # Shift all arguments to the left (original $1 gets lost)
    ARRin=("$@")    #get the second input2 in the form of an array
    ProperInputFlag="false"
    while [[ "$ProperInputFlag" == "false" ]]; do
        echo "enter input and then press enter" 
        read UserInput
        index=0
        while [[ $index < $InputNum ]]; do
            if [[ "${ARRin[index]}" == "$UserInput" ]]; then
                echo $UserInput #we do echo because this is how it is returned back (see https://stackoverflow.com/a/17336953/4441211)
                ProperInputFlag="true"  #this will cause the while loop to break and exit the function 
            fi
            index=$((index+1))
        done
        if [[ "$ProperInputFlag" == "false" ]]; then
            echo "Invalid input. Please enter one of these options:"
            index=0
            while [[ $index < $InputNum ]]; do
                echo "Option1 $((index+1)): " ${ARRin[index]}
                index=$((index+1))
            done
        fi
    done
}

I use the function this way:

ARRAY=("a" "b")
testing=$(ReceiveValidInput 2 "${ARRAY[@]}")
echo "Received: "$testing
read -p "press enter to exit"
exit 1

And it does not work. This syntax testing=$(ReceiveValidInput 2 "${ARRAY[@]}") simply causes the script to get stuck and nothing happens.

However, if I run ReceiveValidInput 2 "${ARRAY[@]}" then the function gets called and everything work except I don't get to capture the "return" value.

Any idea how to write the syntax correctly for calling the function so that I can obtain the "return" value which is technically echoed back?

Upvotes: 0

Views: 154

Answers (2)

markp-fuso
markp-fuso

Reputation: 34054

@GordonDavisson has addressed the issue of why the current function call gets stuck and appears to do nothing.

So, how to make the function call (without getting 'stuck') and provide the calling process with the 'return value'?

Assuming the value to be returned by the function is the value in the UserInput variable (ie, echo $UserInput), you can use the fact that a function call does not invoke a subprocess which in turn means that variable assignments made in the function are available to the parent/calling process.

NOTE: wrapping a function call in $(..) will invoke a subprocess which in turn means the parent/calling process won't have access to values assigned in the function call

Here's an example

$ myfunc () {
x=5 
}

$ x=6
$ myfunc             # call the function; no subprocess involved
$ echo "${x}"
5                    # value set by the function is available to the parent

$ x=9
$ y=$(myfunc)        # call the function in a subprocess
$ echo "${x}"
9                    # x=5, set by the function, is not available to the parent

Pulling this info into the current set of code:

$ function ReceiveValidInput() { .... }      # no changes to the function definition
$ ARRAY=("a" "b")                            # same array assignment
$ ReceiveValidInput 2 "${ARRAY[@]}"          # call the function
$ echo "Received: ${UserInput}"              # reference 'UserInput' value that was set within the function call


If there's a need to let the parent/calling process know there was a problem then the function can return an integer to the parent/calling process.

Calling the myfunc() function (as defined above):

$ myfunc
$ rc=$?
$ echo $rc
0                     # default return code when the last command
                      # executed by the function is successful

We can have the function pass a non-zero return code ($?) via the return command, eg:

$ myfunc2 () {
x=9
return 3              # simple example that will always return '3'; this
                      # can be made dynamic with 'return ${SomeVariable}'
}
$ myfunc2
$ rc=$?
$ echo $rc
3

Upvotes: 2

chepner
chepner

Reputation: 530950

Veering way off topic to point out that you are re-inventing the wheel to some extent, bash has a built-in command for getting interactive input from a set of fixed choices:

select testing in a b; do [[ $testing ]] && break; done

This will display a menu, letting the user choose a value by number, and repeating until a valid choice is made.

Upvotes: 2

Related Questions