Bash function returns unexpected value - bash

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

Related

Bash - check if argument is string

I am attempting to check if an argument is an array with the following code:
if [[ $(declare -p $1) ]] != *-a*;
Here $1 is a string with the value "123". I get the following error message from bash:
`arrays.bash: line 23: declare: 123: not found
This code works if I pass an array as an argument but not a string. I want to verify that the argument is either an array or an associative array. I have no concern with the contents at this point, I only want the type. Any ideas on how to do this?
After all, why worry about the types, if you are relying on it perhaps your approach is wrong or you may need a strong-typed language
% v=1
% declare -p v
declare -- v="1"
% echo $v
1
% echo ${v[#]}
1
% v[1]=2
% declare -p v
declare -a v=([0]="1" [1]="2")
% echo ${v[#]}
1 2
The Error In The Question
You called yourfunction "$a" instead of yourfunction a, when a=123. Don't do that: You need to pass the name of the variable, not its value.
General Solution: Bash 5.x+
Bash 5 has a new feature called parameter transformation, whereby ${parameter#operator} can perform a variety of actions; one of these is checking the type of the parameter.
myfunc() {
[[ -v "$1" ]] || { echo "No variable named $1 exists" >&2; return 1; }
case ${!1#a} in
*a*) echo "Array";;
*A*) echo "Associative array";;
*i*) echo "Integer";;
"") echo "Default string";;
*) echo "Other/unknown flag set: ${!1#a}";;
esac
}
Older Solution
myfunc() {
local typedesc
typedesc=$(declare -p "$1" 2>/dev/null) || {
echo "No variable named $1 is set" >&2
return 1
}
case $typedesc in
"declare -a"*) echo "Array";;
"declare -A"*) echo "Associative array";;
"declare -i"*) echo "Integer";;
"declare --"*) echo "Regular (default) string variable";;
*) echo "Other/unrecognized type";;
esac
}

passing parameters and return values in shell

I have written a function named 'connectTo' which takes paramaters named 'options' and it should return some string by echoing before return.
connectTo ${options}
this works i.e arguments get passed in this but when i write
str=$(connectTo ${options})
then connectTo is working as if no arguments were passed>
I am new to shell scripting and obviously doing something wrong but what?
(remember i need a string to be returned from fuction which cannot be a global variable)
function connectTo(){
local flag=false
local str=""
for i in $#; do
if [ "$flag" = true ]; then
str=$i
flag=false
elif [[ "$i" = "--foo" || "$i" = "-f" ]]; then
flag=true
fi
echo "$i"
done;
if [ "$str" = "" ]; then
echo ""
return 0
fi
echo "found"
return 0
}
In case of connectTo ${options} the arguments get printed while in second case they don't
you should not use '$' sign while assigning a variable. so it should be
str =connectTo [value_of_argument]
$ is used to access the value of variable.

Why reading a function result to variable with v="$()" doesn't work in bash

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}"

How to use function return value in while test in the bash script

Here is my code abstract. I use the result of the comparison between the return_val in func and "true" to decide the while termination condition. But the echoes don't work at all.
#!/bin/bash
func(){
a=$1
b=$2
echo $a
echo $b
if ((a > b))
then
return_val="true"
else
return_val="false"
fi
}
c=1
d=4
while [[ $(func $c $d) && ("$return_val"!="true") ]]
do
((c++))
done
Since you want to see the output from your echo command, you need to separate the stdout output from the return (exit) code:
#!/bin/bash
func(){
local a=$1 b=$2
echo "$a" # stdout output
echo "$b"
if (( a > b )) # see below for how this could be simplified.
then
return 0 # set exit code
else
return 1
fi
}
c=1 d=4
while ! func $c $d
do
((c++))
done
That way, you're free to use the function directly (prefixed with ! to negate the test's outcome), without a command substitution, and let its return (exit) code drive the outcome of the while loop condition (an exit code of 0 meaning success, any other meaning failure).
By using just ! func $c $d as the condition, func's stdout output is simply printed as is.
Addendum: Billy Wayne McCann's answer points out that there's no strict need to use explicit return statements to set the exit code (but there's no need to use test[1]).
(( a > b )) can be used by itself as the last statement in the function, to set the exit code implicitly:
If there's no explicit return statement, it is a function's last command that sets its exit code (the same applies analogously to scripts in the absence of an exit statement).
(( ... )) sets the exit code to 1, if ... evaluates to 0, and 1 otherwise (any non-negative result). A Boolean expression such as a > b results in 1, if the expression is true, and 0 otherwise. Therefore, (( a > b )) by itself does the same thing implicitly that the above if statement does explicitly:
func(){
local a=$1 b=$2
echo "$a" # stdout output
echo "$b"
(( a > b )) # exit code implicitly set to 0, if test *succeeds*, 1 otherwise
}
[1] Using test or [ ... ] is the POSIX-compliant way to evaluate a condition. Unless your code must be POSIX-compliant, Bash's [[ ... ]] and (( ... )) (for arithmetic Boolean tests) are the better choice - see https://stackoverflow.com/a/29320710/45375
Forcing a return of 0 or 1 isn't the proper way to do this. You want to return the true exit status of the test, not a forced return status. There's really no need for the if ... then block.
Functions automatically return their exit status. This exit status of the function is the status of the final statement. Hence, one can place test as the final statement in func.
#!/usr/bin/env bash
func(){
local a=$1
local b=$2
test $a -lt $b #use exit status directly
}
c=1
d=4
while func $c $d #while c is less than d
do
((c++))
done
I would rather have function return a value.
#!/bin/bash
func(){
a=$1
b=$2
echo $a
echo $b
if ((a > b))
then
return 0 # 0 means success
else
return 1 # non-zer0 means failure
fi
}
c=1
d=4
until (func $c $d) #While ! success, do something..
do
((c++))
done
Note: The parenthesis around func $c $d are only for readability. Not required really.

Unix shell functions, command substitution and exit

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" )

Resources