Run command substitution inside function when function is called - bash

I have a bash script which have some helper function inside a shared, common file lib.sh. However, some of these functions look like this:
function check_prs {
local labels
labels=$(hub pr show -h "${BRANCH:-}" -F '%L')
if [[ $PRLABELS == *"DOTHING"* ]]
then true
else false
fi
}
The problem is that the command substitution and labels variable assignment is done when the script is loaded (source lib.sh from my main script), regardless of whether the function is called (or when).
Is there a way to only make these command substitutions when the function is actually called? Or some other way to make it behave as everything else in the normal flow of the script?

Related

Why does function call from PS1 require escaping?

I'm setting my prompt inside of .bash_profile like this
export PS1="\w\$(getBranchName)\n ---->"
My getBranchName function exists, and this works, fine.
My question is, why do I need to escape the call to getBranchName like this \$(getBranchName).
In other words, why doesn't this code work, instead?
export PS1="\w$(getBranchName)\n ---->"
If curious, this is what the getBranchName function looks like
esc="\033"
redf="${esc}[31m"
green="${esc}[32m"
purple="${esc}[35m"
cyanf="${esc}[36m"
reset="${esc}[0m"
getBranchName() {
if [[ "$(__git_ps1 '%s')" == "master" ]]
then
echo -e "${redf}$(__git_ps1)${reset}";
else
echo -e "${cyanf}$(__git_ps1)${reset}";
fi
}
export PS1="\w\$(getBranchName)\n ---->"
You need to escape the dollar because you want to store this exact text in your variable.
Try it by typing echo "$PS1". You should see the exact text : \w$(getBranchName)\n ---->
If you didn't escape it, the function would be evaluated only once, during the allocation.
The bottom line is that PS1 is a special variable : every time you display a new line in the console, the variable is evaluated to extract the display settings.
The PS1 variable is basically a template string (which might contain function calls) which is evaluated each time the prompt is shown.
If you want to evaluate a function each time, so that each prompt shows the result of this new execution, you need to escape the call.
If you would embed the function call directly in the string, the function would be called once immediately (i.e. likely during login) and your PS1 will contain the result of this single function call as evaluated during your initial login. Thus, the value won't be updated again since the function is not called anymore (since the PS1 doesn't contain the function call anymore but only the static result of one).
It's escaped because you want it to run when the shell evaluates $PS1 each time it's displayed, not just during the assignment.
The other expansions (which should be using tput unless you actually like random control codes all over your non-ANSI terminals) you want to be expanded just once, when you assign to PS1.

How do I pass a command parameter in a variable holding the command?

I want to produce the same output as this:
bash utilities.bash "is_net_connected"
But I don't know how to pass "is_net_connected" if command and file is stored in a variable like this:
T=$(bash utilities.bash)
I've tried these but it doesn't seem to work. It's not picking up ${1} in utilities.bash.
$(T) "is_net_connected"
$(T "is_net_connected")
Not the best way to inport but I'm trying to avoid cluttering my main script with function blocks.
T=$(bash utilities.bash) doesn't save the command; it runs the command and saves its output. You want to define a function instead.
T () {
bash utilities.bash "$#"
}
# Or on one line,
# T () { bash utilities.bash "$#"; }
Now
T "is_net_connected"
will run bash utilities.bash with whatever arguments were passed to T. In a case like this, an alias would work the same: alias T='bash utilities.bash'. However, any changes to what T should do will probably require switching from an alias to a function anyway, so you may as well use the function to start. (Plus, you would have to explicitly enable alias expansion in your script.)
You might be tempted to use
T="bash utilities.bash"
$T is_net_connected
Don't be. Unquoted parameter expansions are bad practice that only work in select situations, and you will get bitten eventually if you try to use them with more complicated commands. Use a function; that's why the language supports 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.

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.

Resources