Bash function with parameters - bash

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

Related

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

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.

Getting function returned value

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.

Export not working (from a function called to get its echo)

I have a code like this:
#!/usr/bin/env bash
test_this(){
export ABC="ABC"
echo "some output"
}
final_output="the otput is $(test_this)"
echo "$ABC"
Unfortunately the variable ABC is not being set.
I have to call test_this like that, since in my real program I give some arguments to it, it performs various complicated operations calling various other functions, which on the way export this or that (basing on those arguments), and at the end some output string is assembled to be returned. Calling it two times, once to get exports and once for the output string would be bad.
The question is: what can I do to have both the exports and the output string in place, but just by one call to such a function?
The answer that I am happy with (thank you paxdiablo):
#!/usr/bin/env bash
test_this(){
export ABC="ABC"
export A_VERY_OBSCURE_NAME="some output"
}
test_this
final_output="the otput is $A_VERY_OBSCURE_NAME"
echo "$ABC" #works!
unset A_VERY_OBSCURE_NAME
Yes, it is being set. Unfortunately it's being set in the sub-process that is created by $() to run the test_this function and has no effect on the parent process.
And calling it twice is probably the easiest way to do it, something like (using a "secret" parameter value to dictate behaviour if it needs to be different):
#!/usr/bin/env bash
test_this(){
export ABC="ABC"
if [[ "$1" != "super_sekrit_sauce" ]] ; then
echo "some output"
fi
}
final_output="the output is $(test_this)"
echo "1:$ABC:$final_output"
test_this super_sekrit_sauce
echo "2:$ABC:$final_output"
which outputs:
1::the output is some output
2:ABC:the output is some output
If you really only want to call it once, you could do something like:
#!/usr/bin/env bash
test_this(){
export ABC="ABC"
export OUTPUT="some output"
}
test_this
final_output="the output is ${OUTPUT}"
echo "1:$ABC:$final_output"
In other words, use the same method for extracting output as you did for the other information.

Shell script variable problem

I'm trying to write a shell script to automate a job for me. But i'm currently stuck.
Here's the problem :
I have a variable named var1 (a decreasing number from 25 to 0
and another variable named
var${var1} and this equals to some string.
then when i try to call var${var1} in anywhere in script via echo it fails.
I have tried $[var$var1], ${var$var} and many others but everytime it fails and gives the value of var1 or says operand expected error.
Thanks for your help
It's probably better if you use an array, but you can use indirection:
var25="some string"
var1=25
indirect_var="var$var1"
echo ${!indirect_var} # echoes "some string"
There's only one round of variable expansion, so you can't do it directly. You could use eval:
eval echo \${var$var1}
A better solution is to use an array:
i=5
var[$i]='foo'
echo ${var[$i]}
It sounds like you need bash variable indirection. Take a look at the link below.
http://mywiki.wooledge.org/BashFAQ/006

Resources