Please explain me about how to use unix shell function correctly.
For example we have following functions f and g:
f()
{
#do something
return $code
}
g()
{
print $something
}
We can use function f in the next way:
f
if [[ $? -eq 0 ]]; then
#do 1
else
#do 2
fi
This function performs some work and exits with some exit status.
And we can analyze this exit status.
We can use function g in the next way:
g
or
result=$(g)
if [[ $result = "something" ]]; then
#do something
fi
In first case we just called function.
In the second case we used command substitution to assign all text that function prints to stdout to variable result.
But what if there is following function:
z()
{
user=$1
type=$2
if [[ $type = "customer" ]]; then
result=$(/somedir/someapp -u $user)
if [[ $result = "" ]]; then
#something goes wrong
#I do not want to continue
#I want to stop whole script
exit 1
else
print $result
fi
else
print "worker"
fi
}
I can use function z in the next way:
z
If something goes wrong then whole script will be stopped.
But what if somebody uses this function in command substitution:
result=$(z)
In this case if someapp returns empty string script will not be stopped.
Is it not correct approach to use exit in functions?
I don't have a way to test this right now, but ksh (maybe bash too), can scope variables inside functions.
z()
{
typeset result
user=$1
type=$2
if [[ $type = "customer" ]]; then
result=$(/somedir/someapp -u $user)
if [[ $result = "" ]]; then
#something goes wrong
#I do not want to continue
#I want to stop whole script
exit 1
else
print $result
fi
else
print "worker"
fi
}
Notice the insertion of typeset result near the top.
You may need to use the alternate declartion of function for this feature to work, i.e.
function z {
#....
}
I hope this helps.
You could also do something like
result=$(z ; "eval retCode=\$? ; echo \$retCode" )
Related
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?
#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
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.
function() {
if something_that_will_fail; then
irrelevantcode
else
echo "should be here"
false
fi
}
echo $function
This outputs "should be here". How do I get the false value?
You seem to be confusing the output with returned value.
$function is a variable, you don't seem to populate it anywhere. To populate it with the output of the function, use
output=$(function_call)
The return value of a function can be retrieved from the special variable $?.
function_call
value=$?
If you want to use it in a condition, you often don't need the variable at all, as you can run the function directly in the condition:
function_call
if (( $? )) ; then
echo There was an error
else
echo Everything OK
fi
can be shortened to
if function_call ; then
echo Everything OK
else
echo There was an error
fi
Bash has no built-in boolean variables. Further, calling a function a la $function_name also seems wrong to me.
I am not sure, what you are trying to achieve, but you have two options there:
You can output strings within your function and retrieve them afterwards.
Additionally, you can also set the exit status of your function via the return key (this is what return is all about); here you can also specify a status code within a range 0-255 for your return, where 0 indicates that function terminated successfully, and all other numbers indicate the opposite.
Example:
#!/usr/bin/env bash
function is_greater() {
local value1=$1
local value2=$2
if [[ $value1 -gt $value2 ]]; then
printf "%s is greater than %s! \n" $value1 $value2
return 0
else
printf "%s is NOT greater than %s! \n" $value1 $value2
return 1
fi
}
what_func_says=$(is_greater 21 42)
func_exit_status=$?
if [[ $func_exit_status -eq 0 ]]; then
echo $what_func_says
printf "success code: %s \n" $func_exit_status
else
echo $what_func_says
printf "error code: %s \n" $func_exit_status
fi
Output:
21 is NOT greater than 42!
error code: 1
I want to run a function from inside my if statement condition, but I'm unsure on how it should be written in one line.
My function looks like this:
MyFunction(){
fuctionArg1=$1
functionArg2=$2
echo "Inside the function, arg1 is $functionArg1"
echo "Inside the function, arg2 is $functionArg2"
return 1
}
I currently have this if statement:
arg1=10
arg2=20
MyFunction $arg1 $arg2
if [ $? == 1 ] ; then
echo "Hello"
else
echo "Nothing"
fi
But why doesn't something like this below work? It currently only seems to return as 1 even when the MyFunction return value is set to 0:
arg1=10
arg2=20
if [ "$(MyFunction "$arg1" "$arg2")" == 1 ] ; then
echo "Hello"
else
echo "Nothing"
fi
Using command substitution I don't think this is possible.
Command substitution allows the output of a command to replace the
command itself.
So what will be tested in the if statement would be the output from the function (the echo statements).
Without the echo statements, there is no output, so the if statement is always evaluated as false.
What you can do is just pass the function call as an argument to if. Note though that the if expression is evaluated as true if return 0, false if return 1. Which I believe is the Linux convention - to return 0 indicates success, non-zero indicates an error state.
if MyFunction "$arg1" "$arg2; then
echo "Hello"
else
echo "Nothing"
fi
In MyFunction return 0. Or return 1 and negate the expression thus:
if ! MyFunction "$arg1" "$arg2; then
I would like to print the factorial of number read from stdin but I cannot do it this way (this prints empty line):
#!/bin/bash
factorial()
{
if [ $1 -le 1 ]
then
return 1
else
factorial $[$1-1]
return $[$1*$?]
fi
}
read num
ret="$(factorial $num)"
echo "${ret}"
This way worked but I feel it's a bit worse (as I cannot save the variable for later):
factorial $num
echo $?
Why is the first method not working? (link to highly upvoted answer on SO that explains it)
The first method works if you echo the result in the factorial function rather than trying to return it.
The $(...) syntax evaluates to the output of the command you run. Since your code, as it is, has no output, ret will be empty.
#!/bin/bash
factorial()
{
if [ $1 -le 1 ]
then
echo 1
else
part=$(factorial $(($1-1)))
echo $(($1*$part))
fi
}
read num
ret="$(factorial $num)"
echo "${ret}"
I can't understand why userType is not changing.
I know for certain it's successfully reaching determineType, but it isn't changing the value to "bbb" when I try to print out userType later.
userType="aaa"
function determineType {
userType="bbb"
}
function checkUser {
cat users.csv | \
while read userLine; do
if [[ $userLine =~ .*$user.* ]]
then
determineType
echo "1"
fi
done
echo "0"
}
As soulseekah said in a comment, your while loop is executed in a subshell. Instead, do (and, as a benefit, you get rid of the useless use of cat):
userType="aaa"
determineType() {
userType="bbb"
}
checkUser() {
while read userLine; do
if [[ $userLine = *$user* ]]; then
determineType
return 1
fi
done < users.csv
return 0
}
Note. I also changed a few things:
got rid of the useless regexp since the same can be achieved with globbing,
used more common ways of defining functions in bash,
used return instead of echo for returning values: you'd run into the same problem again with an echo: you'd probably use your function checkUser in another subshell to obtain the value returned by the echo.
You are using a pipe, which launch the while .. do in a subshell.
Changing the value of a variable in a subshell won't affect the original variable
You should replace the:
function checkUser {
cat users.csv | \
while read userLine; do
if [[ $userLine =~ .*$user.* ]]
then
determineType
echo "1"
fi
done
echo "0"
}
with
function checkUser {
while read userLine; do
if [[ $userLine =~ .*$user.* ]]
then
determineType
echo "1"
fi
done < users.csv
echo "0"
}
(This also get rid of a Useless Use Of Cat)