Getting function returned value - bash

Suppose I have a function "func" which gets two arguments "a" and "b" and returns something.
For some reason I can't manage echo what I get from the function.
This is what I tried:
value=$(func $a $b)
echo $value

Since you didn't show your function definition, this is only a guess, but I'm pretty confident it's a good one.
You need to understand that a function in shell fits into the language as if it was a command, i.e. a separate program, and the return keyword is analogous to exit. You can only return small integers, and the caller sees them in $?, just like the exit value of any other command.
Functions can also output a result to stdout by using echo (or any other command that prints to stdout), and then the result can be caputred with $(func args ...)
So you probably just need to change return to echo in your function.

Related

Bash function with parameters

I am trying to understand how to work with functions (that receive a argument) in bash. I did not get my code to work, the following code exemplifies my difficulties:
#!/bin/bash
fruit_code () {
if [[ "$1" == "apple" ]]; then
code=1
else
code=0
fi
return $code
}
for var in apple orange banana apple; do
code=fruit_code $var
echo $code
done
The shell (bash) complains saying:
apple: command not found
orange: command not found
banana: command not found
apple: command not found
So it seems the passing of parameters is not working propperly. I can not see where I am going wrong. Any help is very much appreciated. Why does it not work? What changes shoyld I do to make it work? Wish to thank you all in advance.
Kind regards
Miguel
The first problem is that if you want to execute a command and capture its output to a variable, you need to use command substitution:
code=$(fruit_code "$var")
The second problem is that your function doesn't write anything to standard output; it returns an exit status. That is automatically assigned to $? after you call the function. Use this instead:
fruit_code "$var"
echo $?
Finally, the convention in shell is for a 0 exit status to indicate success and a non-zero value failure. In this case, your function "succeeds" if the argument is not apple.
You have a syntax error in this line:
code=fruit_code $var
A syntax like this:
foo=bar quux
means that you want to run command quux in an environment where variable foo has value bar.
In your case, quux is $var, so it will take the value of $var and try to run it as a command. That's why you are getting errors saying apple, orange, etc. are not commands.
I think what you actually want is to run fruit_code $var and store the output in variable code, right?
In that case you can use this:
code=$(fruit_code $var)
or this:
code=`fruit_code $var`
I prefer the former because it looks clearer to me and allows easy nesting.
Update: Returning strings in functions
Also, as noted by chepner on his answer and as you also noticed in a comment below, you are trying to use return for returning that string value as the result of the function, but return can only be used for returning numeric values as the result of the function (success / error / etc.) so you try to work it around by using numeric values that represent the strings you want to return.
That's overcomplex.
If you want to return a string, use echo instead and you can use $() or `` as explained above to capture the string returned by your function.
So, putting all this together, your code would look like this:
#!/bin/bash
fruit_code () {
echo $1
}
for var in apple orange banana apple; do
code=$(fruit_code $var)
echo $code
done

Delayed expansion of composite variable in Bash

I'm defining a variable as a composition of other variables and some text, and I'm trying to get this variable to not expand its containing variables on the assigning. But I want it to expand when called later. That way I could reuse the same template to print different results as the inner variables keep changing. I'm truing to avoid eval as much as possible as I will be receiving some of the inner variables from third parties, and I do not know what to expect.
My use case, as below, is to have some "calling stack" so I can log all messages with the same format and keep a record of the script, function, and line of the logged message in some format like this: script.sh:this_function:42.
My attempted solution
called.sh:
#!/bin/bash
SCRIPT_NAME="`basename "${BASH_SOURCE[0]}"`"
CURR_STACK="${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"
echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"
echo "${CURR_STACK}"
echo
function _func_1 {
echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"
echo "${CURR_STACK}"
}
_func_1
So, I intend to get the same results while printing the "${CURR_STACK}" as when printing the previous line.
If there is some built-in or other clever way to log this 'call stack', by all means, let me know! I'll gladly wave my code good-bye, but I'd still like to know how to prevent the variables from expanding right away on the assigning of CURR_STACK, but still keep them able to expand further ahead.
Am I missing some shopt?
What I've tried:
Case 1 (expanding on line 4):
CURR_STACK="${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]}"
CURR_STACK="`echo "${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}"`"
CURR_STACK="`echo "\${SCRIPT_NAME}:\${FUNCNAME[0]}:\${LINENO[0]}"`"
called.sh::7 <------------------| These are control lines
called.sh::4 <---------------. .------------| With the results I expect to get.
X
called.sh:_func_1:12 <---´ `-------| Both indicate that the values expanded
called.sh::4 <-------------------------| on line 4 - when CURR_STACK was set.
Case 2 (not expanding at all):
CURR_STACK="\${SCRIPT_NAME}:\${FUNNAME[0]}:\${LINENO[0]}"
CURR_STACK=\${SCRIPT_NAME}:\${FUNCNAME[0]}:\${LINENO[0]}
CURR_STACK="`echo '${SCRIPT_NAME}:${FUNCNAME[0]}:${LINENO[0]}'`"
called.sh::7
${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]} <-------.----| No expansion at all!...
/
called.sh::12 /
${SCRIPT_NAME}:${FUNNAME[0]}:${LINENO[0]} <----´
Shell variables are store plain inert text(*), not executable code; there isn't really any concept of delayed evaluation here. To make something that does something when used, create a function instead of a variable:
print_curr_stack() {
echo "$(basename "${BASH_SOURCE[1]}"):${FUNCNAME[1]}:${BASH_LINENO[0]}"
}
# ...
echo "We are now at $(print_curr_stack)"
# Or just run it directly:
print_curr_stack
Note: using BASH_SOURCE[1] and FUNCNAME[1] gets info about context the function was run from, rather than where it is in the function itself. But for some reason I'm not clear on, BASH_LINENO[1] gets the wrong info, and BASH_LINENO[0] is what you want.
You could also write it to allow the caller to specify additional text to print:
print_curr_stack() {
echo "$#" "$(basename "${BASH_SOURCE[1]}"):${FUNCNAME[1]}:${BASH_LINENO[0]}"
}
# ...
print_curr_stack "We are now at"
(* There's an exception to what I said about variables just contain inert text: some variables -- like $LINENO, $RANDOM, etc -- are handled specially by the shell itself. But you can't create new ones like this except by modifying the shell itself.)
Are you familiar with eval?
$ a=this; b=is; c=a; d=test;
$ e='echo "$a $b $c $d"';
$ eval $e;
this is a test
$ b='is NOT'; # modify one of the variables
$ eval $e;
this is NOT a test
$ f=$(eval $e); # capture the value of the "eval" statement
$ echo $f;
this is NOT a test

Export serialized array in bash

I'm trying to serialize an array in bash and then export it:
function serialize
{
for i in ${1[#]}; do
ret+=$i" "
done
return ${ret::-1}
}
MEASUREMENT_OUTPUT_FILES=( t1DQ.txt t1CS.txt t2RXe.txt t2e.txt )
export MEASUREMENT_OUTPUT_FILES=${serialize MEASUREMENT_OUTPUT_FILES[#]}
The code produces the following error:
MEASUREMENT_OUTPUT_FILES=${serialize MEASUREMENT_OUTPUT_FILES[#]}: bad
substitution
Any ideas what the correct syntax (error in the last line starting with export) would be?
I believe you want:
export MEASUREMENT_OUTPUT_FILES=$(serialize "${MEASUREMENT_OUTPUT_FILES[#]}")
(where $(...) is a notation for command substitution).
That said, your command is actually equivalent to
export MEASUREMENT_OUTPUT_FILES="${MEASUREMENT_OUTPUT_FILES[*]}"
so you don't need the serialize function unless you want to improve your serialization logic. (Which you should consider doing, IMHO: just joining with a space is error-prone, because what if one of the arguments includes a space?)
Edited to add: Also, I don't know how we all missed this before, but this:
return ${ret::-1}
actually needs to be this:
echo "${ret::-1}"
or this:
printf %s "${ret::-1}"
since return is for setting the exit status of a function, which must be an integer. (It's intended for indicating success, zero, vs. failure, nonzero, though some commands assign special meanings to multiple nonzero values.) What you want is for your function to "print" the files, so you can capture them.

return a value from shell script, into another shell script

I know how to return an exit code, but I would like to return the result of an operation done in a shell script function, so I can eventually use it in another script or function.
Something like
var1=$(myfunction)
function2 var1
Where myfunction could be something like A+B=C
I looked into "return", but it will return a code, not a value.
I am looking into various sites that show how to write functions, but I don't see how you actually return values.
In C++ you would use return "variable name", but shell script won't allow this. It says that the variable do not exist (which is logical, it is a variable created in a function, so when the function is released, that memory space assigned to it is gone). Can't use global variables since the function may be in one script and the calling function that needs the return value, may be in a different one.
myfunction could be something like A+B=C
Just echo the result:
$ myfunction() { echo $(($1+$2)); }
The above myfunction adds two numbers and echoes the result.
The return value can then be captured just as you had it:
$ var=$(myfunction 12 5)
$ echo $var
17
The construct var=$(myfunction) captures the standard out from myfunction and saves it in var. Thus, when you want to return something from myfunction, just send it to standard, like we did with echo in the example above.
In cases where you want the return value to be carefully formatted, you should consider using printf in place of echo.
More: How to return multiple values
Let's define a function that produces two outputs:
$ f() { echo "output1" ; echo "output2" ; }
$ f
output1
output2
If you want to get those values back separately, the most reliable method is to use bash's arrays:
$ a=($(f))
The above executes f, via $(f) and saves the results in an array called a. We can see what is in a by using declare -p:
$ declare -p a
declare -a a='([0]="output1" [1]="output2")'
I use the same sorta thing for returning values from other scripts to my main script like the title suggests.
At the end of the 2nd script, I echo the variable I want to return to the main script:
#!/bin/bash
# This is the Second Script.
# Store the variables passed from the main script:
VAR1_FROM_MAIN_SCRIPT=$1
VAR2_FROM_MAIN_SCRIPT=$2
# Add the 2 variables and store as another variable to return:
RETURN_THIS=$(($VAR1_FROM_MAIN_SCRIPT + VAR2_FROM_MAIN_SCRIPT))
# This is the variable you are sending back to the main script:
echo "$RETURN_THIS" #<---- This won't print to screen!!!
Then in the main script I pass in a couple variables to, and execute, the 2nd script like this:
#!/bin/bash
# This is the Main Script.
PASS_VAR1_TO_SCRIPT=1
PASS_VAR2_TO_SCRIPT=2
# Call the second script and store it's results in this variable:
RETURN_VARIABLE=$(./secondScriptName "$PASS_VAR1_TO_SCRIPT" "$PASS_VAR2_TO_SCRIPT")
# Display the returned variable from the second script:
echo $RETURN_VARIABLE #<---- Will display 3
The reason the echo in the second script won't print to screen, is because it's running that second script in a subshell from the RETURN_VARIABLE... I know my explanation of the subshell sucks, but that's besides the point...
Also, I know you can source the other script, but this might help others.
In shell scripting you don't return a value but just echo (print) it and caller would capture the output of your script/function to grab the returned value.
Example:
dateval=$(date)
echo $dateval
Wed Apr 23 18:35:45 EDT 2014
Instead of date you can place your function or your shell script.

Shell scripting return values not correct, why?

In a shell script I wrote to test how functions are returning values I came across an odd unexpected behavior. The code below assumes that when entering the function fnttmpfile the first echo statement would print to the console and then the second echo statement would actually return the string to the calling main. Well that's what I assumed, but I was wrong!
#!/bin/sh
fntmpfile() {
TMPFILE=/tmp/$1.$$
echo "This is my temp file dude!"
echo "$TMPFILE"
}
mainname=main
retval=$(fntmpfile "$mainname")
echo "main retval=$retval"
What actually happens is the reverse. The first echo goes to the calling function and the second echo goes to STDOUT. why is this and is there a better way....
main retval=This is my temp file dude!
/tmp/main.19121
The whole reason for this test is because I am writing a shell script to do some database backups and decided to use small functions to do specific things, ya know make it clean instead of spaghetti code. One of the functions I was using was this:
log_to_console() {
# arg1 = calling function name
# arg2 = message to log
printf "$1 - $2\n"
}
The whole problem with this is that the function that was returning a string value is getting the log_to_console output instead depending on the order of things. I guess this is one of those gotcha things about shell scripting that I wasn't aware of.
No, what's happening is that you are running your function, and it outputs two lines to stdout:
This is my temp file dude!
/tmp/main.4059
When you run it $(), bash will intercept the output and store it in the value. The string that is stored in the variable contains the first linebreak (the last one is removed). So what is really in your "retval" variable is the following C-style string:
"This is my temp file dude!\n/tmp/main.4059"
This is not really returning a string (can't do that in a shell script), it's just capturing whatever output your function returns. Which is why it doesn't work. Call your function normally if you want to log to console.

Resources