Changing global var inside function doesnt mutate global variable [duplicate] - linux
I'm working with this:
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
I have a script like below:
#!/bin/bash
e=2
function test1() {
e=4
echo "hello"
}
test1
echo "$e"
Which returns:
hello
4
But if I assign the result of the function to a variable, the global variable e is not modified:
#!/bin/bash
e=2
function test1() {
e=4
echo "hello"
}
ret=$(test1)
echo "$ret"
echo "$e"
Returns:
hello
2
I've heard of the use of eval in this case, so I did this in test1:
eval 'e=4'
But the same result.
Could you explain me why it is not modified? How could I save the echo of the test1 function in ret and modify the global variable too?
When you use a command substitution (i.e., the $(...) construct), you are creating a subshell. Subshells inherit variables from their parent shells, but this only works one way: A subshell cannot modify the environment of its parent shell.
Your variable e is set within a subshell, but not the parent shell. There are two ways to pass values from a subshell to its parent. First, you can output something to stdout, then capture it with a command substitution:
myfunc() {
echo "Hello"
}
var="$(myfunc)"
echo "$var"
The above outputs:
Hello
For a numerical value in the range of 0 through 255, you can use return to pass the number as the exit status:
mysecondfunc() {
echo "Hello"
return 4
}
var="$(mysecondfunc)"
num_var=$?
echo "$var - num is $num_var"
This outputs:
Hello - num is 4
This needs bash 4.1 if you use {fd} or local -n.
The rest should work in bash 3.x I hope. I am not completely sure due to printf %q - this might be a bash 4 feature.
Summary
Your example can be modified as follows to archive the desired effect:
# Add following 4 lines:
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
passback() { _passback "$#" "$?"; }
_capture() { { out="$("${#:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)"; }
capture() { eval "$(_capture "$#")"; }
e=2
# Add following line, called "Annotation"
function test1_() { passback e; }
function test1() {
e=4
echo "hello"
}
# Change following line to:
capture ret test1
echo "$ret"
echo "$e"
prints as desired:
hello
4
Note that this solution:
Works for e=1000, too.
Preserves $? if you need $?
The only bad sideffects are:
It needs a modern bash.
It forks quite more often.
It needs the annotation (named after your function, with an added _)
It sacrifices file descriptor 3.
You can change it to another FD if you need that.
In _capture just replace all occurances of 3 with another (higher) number.
The following (which is quite long, sorry for that) hopefully explains, how to adpot this recipe to other scripts, too.
The problem
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
d1=$(d)
d2=$(d)
d3=$(d)
d4=$(d)
echo $x $d1 $d2 $d3 $d4
outputs
0 20171129-123521 20171129-123521 20171129-123521 20171129-123521
while the wanted output is
4 20171129-123521 20171129-123521 20171129-123521 20171129-123521
The cause of the problem
Shell variables (or generally speaking, the environment) is passed from parental processes to child processes, but not vice versa.
If you do output capturing, this usually is run in a subshell, so passing back variables is difficult.
Some even tell you, that it is impossible to fix. This is wrong, but it is a long known difficult to solve problem.
There are several ways on how to solve it best, this depends on your needs.
Here is a step by step guide on how to do it.
Passing back variables into the parental shell
There is a way to pass back variables to a parental shell. However this is a dangerous path, because this uses eval. If done improperly, you risk many evil things. But if done properly, this is perfectly safe, provided that there is no bug in bash.
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
d() { let x++; d=$(date +%Y%m%d-%H%M%S); _passback x d; }
x=0
eval `d`
d1=$d
eval `d`
d2=$d
eval `d`
d3=$d
eval `d`
d4=$d
echo $x $d1 $d2 $d3 $d4
prints
4 20171129-124945 20171129-124945 20171129-124945 20171129-124945
Note that this works for dangerous things, too:
danger() { danger="$*"; passback danger; }
eval `danger '; /bin/echo *'`
echo "$danger"
prints
; /bin/echo *
This is due to printf '%q', which quotes everything such, that you can re-use it in a shell context safely.
But this is a pain in the a..
This does not only look ugly, it also is much to type, so it is error prone. Just one single mistake and you are doomed, right?
Well, we are at shell level, so you can improve it. Just think about an interface you want to see, and then you can implement it.
Augment, how the shell processes things
Let's go a step back and think about some API which allows us to easily express, what we want to do.
Well, what do we want do do with the d() function?
We want to capture the output into a variable.
OK, then let's implement an API for exactly this:
# This needs a modern bash 4.3 (see "help declare" if "-n" is present,
# we get rid of it below anyway).
: capture VARIABLE command args..
capture()
{
local -n output="$1"
shift
output="$("$#")"
}
Now, instead of writing
d1=$(d)
we can write
capture d1 d
Well, this looks like we haven't changed much, as, again, the variables are not passed back from d into the parent shell, and we need to type a bit more.
However now we can throw the full power of the shell at it, as it is nicely wrapped in a function.
Think about an easy to reuse interface
A second thing is, that we want to be DRY (Don't Repeat Yourself).
So we definitively do not want to type something like
x=0
capture1 x d1 d
capture1 x d2 d
capture1 x d3 d
capture1 x d4 d
echo $x $d1 $d2 $d3 $d4
The x here is not only redundant, it's error prone to always repeate in the correct context. What if you use it 1000 times in a script and then add a variable? You definitively do not want to alter all the 1000 locations where a call to d is involved.
So leave the x away, so we can write:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
d() { let x++; output=$(date +%Y%m%d-%H%M%S); _passback output x; }
xcapture() { local -n output="$1"; eval "$("${#:2}")"; }
x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4
outputs
4 20171129-132414 20171129-132414 20171129-132414 20171129-132414
This already looks very good. (But there still is the local -n which does not work in oder common bash 3.x)
Avoid changing d()
The last solution has some big flaws:
d() needs to be altered
It needs to use some internal details of xcapture to pass the output.
Note that this shadows (burns) one variable named output,
so we can never pass this one back.
It needs to cooperate with _passback
Can we get rid of this, too?
Of course, we can! We are in a shell, so there is everything we need to get this done.
If you look a bit closer to the call to eval you can see, that we have 100% control at this location. "Inside" the eval we are in a subshell,
so we can do everything we want without fear of doing something bad to the parental shell.
Yeah, nice, so let's add another wrapper, now directly inside the eval:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
# !DO NOT USE!
_xcapture() { "${#:2}" > >(printf "%q=%q;" "$1" "$(cat)"); _passback x; } # !DO NOT USE!
# !DO NOT USE!
xcapture() { eval "$(_xcapture "$#")"; }
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4
prints
4 20171129-132414 20171129-132414 20171129-132414 20171129-132414
However, this, again, has some major drawback:
The !DO NOT USE! markers are there,
because there is a very bad race condition in this,
which you cannot see easily:
The >(printf ..) is a background job. So it might still
execute while the _passback x is running.
You can see this yourself if you add a sleep 1; before printf or _passback.
_xcapture a d; echo then outputs x or a first, respectively.
The _passback x should not be part of _xcapture,
because this makes it difficult to reuse that recipe.
Also we have some unneded fork here (the $(cat)),
but as this solution is !DO NOT USE! I took the shortest route.
However, this shows, that we can do it, without modification to d() (and without local -n)!
Please note that we not neccessarily need _xcapture at all,
as we could have written everyting right in the eval.
However doing this usually isn't very readable.
And if you come back to your script in a few years,
you probably want to be able to read it again without much trouble.
Fix the race
Now let's fix the race condition.
The trick could be to wait until printf has closed it's STDOUT, and then output x.
There are many ways to archive this:
You cannot use shell pipes, because pipes run in different processes.
One can use temporary files,
or something like a lock file or a fifo. This allows to wait for the lock or fifo,
or different channels, to output the information, and then assemble the output in some correct sequence.
Following the last path could look like (note that it does the printf last because this works better here):
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
_xcapture() { { printf "%q=%q;" "$1" "$("${#:2}" 3<&-; _passback x >&3)"; } 3>&1; }
xcapture() { eval "$(_xcapture "$#")"; }
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4
outputs
4 20171129-144845 20171129-144845 20171129-144845 20171129-144845
Why is this correct?
_passback x directly talks to STDOUT.
However, as STDOUT needs to be captured in the inner command,
we first "save" it into FD3 (you can use others, of course) with '3>&1'
and then reuse it with >&3.
The $("${#:2}" 3<&-; _passback x >&3) finishes after the _passback,
when the subshell closes STDOUT.
So the printf cannot happen before the _passback,
regardless how long _passback takes.
Note that the printf command is not executed before the complete
commandline is assembled, so we cannot see artefacts from printf,
independently how printf is implemented.
Hence first _passback executes, then the printf.
This resolves the race, sacrificing one fixed file descriptor 3.
You can, of course, choose another file descriptor in the case,
that FD3 is not free in your shellscript.
Please also note the 3<&- which protects FD3 to be passed to the function.
Make it more generic
_capture contains parts, which belong to d(), which is bad,
from a reusability perspective. How to solve this?
Well, do it the desparate way by introducing one more thing,
an additional function, which must return the right things,
which is named after the original function with _ attached.
This function is called after the real function, and can augment things.
This way, this can be read as some annotation, so it is very readable:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
_capture() { { printf "%q=%q;" "$1" "$("${#:2}" 3<&-; "$2_" >&3)"; } 3>&1; }
capture() { eval "$(_capture "$#")"; }
d_() { _passback x; }
d() { let x++; date +%Y%m%d-%H%M%S; }
x=0
capture d1 d
capture d2 d
capture d3 d
capture d4 d
echo $x $d1 $d2 $d3 $d4
still prints
4 20171129-151954 20171129-151954 20171129-151954 20171129-151954
Allow access to the return-code
There is only on bit missing:
v=$(fn) sets $? to what fn returned. So you probably want this, too.
It needs some bigger tweaking, though:
# This is all the interface you need.
# Remember, that this burns FD=3!
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
passback() { _passback "$#" "$?"; }
_capture() { { out="$("${#:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)"; }
capture() { eval "$(_capture "$#")"; }
# Here is your function, annotated with which sideffects it has.
fails_() { passback x y; }
fails() { x=$1; y=69; echo FAIL; return 23; }
# And now the code which uses it all
x=0
y=0
capture wtf fails 42
echo $? $x $y $wtf
prints
23 42 69 FAIL
There is still a lot room for improvement
_passback() can be elmininated with passback() { set -- "$#" "$?"; while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
_capture() can be eliminated with capture() { eval "$({ out="$("${#:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)")"; }
The solution pollutes a file descriptor (here 3) by using it internally.
You need to keep that in mind if you happen to pass FDs.
Note thatbash 4.1 and above has {fd} to use some unused FD.
(Perhaps I will add a solution here when I come around.)
Note that this is why I use to put it in separate functions like _capture, because stuffing this all into one line is possible, but makes it increasingly harder to read and understand
Perhaps you want to capture STDERR of the called function, too.
Or you want to even pass in and out more than one filedescriptor
from and to variables.
I have no solution yet, however here is a way to catch more than one FD, so we can probably pass back the variables this way, too.
Also do not forget:
This must call a shell function, not an external command.
There is no easy way to pass environment variables out of external commands.
(With LD_PRELOAD= it should be possible, though!)
But this then is something completely different.
Last words
This is not the only possible solution. It is one example to a solution.
As always you have many ways to express things in the shell.
So feel free to improve and find something better.
The solution presented here is quite far from being perfect:
It was nearly not tested at all, so please forgive typos.
There is a lot of room for improvement, see above.
It uses many features from modern bash, so probably is hard to port to other shells.
And there might be some quirks I haven't thought about.
However I think it is quite easy to use:
Add just 4 lines of "library".
Add just 1 line of "annotation" for your shell function.
Sacrifices just one file descriptor temporarily.
And each step should be easy to understand even years later.
Maybe you can use a file, write to file inside function, read from file after it. I have changed e to an array. In this example blanks are used as separator when reading back the array.
#!/bin/bash
declare -a e
e[0]="first"
e[1]="secondddd"
function test1 () {
e[2]="third"
e[1]="second"
echo "${e[#]}" > /tmp/tempout
echo hi
}
ret=$(test1)
echo "$ret"
read -r -a e < /tmp/tempout
echo "${e[#]}"
echo "${e[0]}"
echo "${e[1]}"
echo "${e[2]}"
Output:
hi
first second third
first
second
third
What you are doing, you are executing test1
$(test1)
in a sub-shell( child shell ) and Child shells cannot modify anything in parent.
You can find it in bash manual
Please Check: Things results in a subshell here
I had a similar problem when I wanted to remove temporary files I had created automatically. The solution I came up with was not to use command substitution, but rather to pass the name of the variable, that should take the final result, into the function. E.g.
#!/usr/bin/env bash
# array that keeps track of tmp-files
remove_later=()
# function that manages tmp-files
new_tmp_file() {
file=$(mktemp)
remove_later+=( "$file" )
# assign value (safe form of `eval "$1=$file"`)
printf -v "$1" -- "$file"
}
# function to remove all tmp-files
remove_tmp_files() { rm -- "${remove_later[#]}"; }
# define trap to remove all tmp-files upon EXIT
trap remove_tmp_files EXIT
# generate tmp-files
new_tmp_file tmpfile1
new_tmp_file tmpfile2
So, adapting this to the OP, it would be:
#!/usr/bin/env bash
e=2
function test1() {
e=4
printf -v "$1" -- "hello"
}
test1 ret
echo "$ret"
echo "$e"
Works and has no restrictions on the "return value".
Assuming that local -n is available, the following script lets the function test1 modify a global variable:
#!/bin/bash
e=2
function test1() {
local -n var=$1
var=4
echo "hello"
}
test1 e
echo "$e"
Which gives the following output:
hello
4
I'm not sure if this works on your terminal, but I found out that if you don't provide any outputs whatsoever it gets naturally treated as a void function, and can make global variable changes.
Here's the code I used:
let ran1=$(( (1<<63)-1)/3 ))
let ran2=$(( (1<<63)-1)/5 ))
let c=0
function randomize {
c=$(( ran1+ran2 ))
ran2=$ran1
ran1=$c
c=$(( c > 0 ))
}
It's a simple randomizer for games that effectively modifies the needed variables.
It's because command substitution is performed in a subshell, so while the subshell inherits the variables, changes to them are lost when the subshell ends.
Reference:
Command substitution, commands grouped with parentheses, and asynchronous commands are invoked in a subshell environment that is a duplicate of the shell environment
A solution to this problem, without having to introduce complex functions and heavily modify the original one, is to store the value in a temporary file and read / write it when needed.
This approach helped me greatly when I had to mock a bash function called multiple times in a bats test case.
For example, you could have:
# Usage read_value path_to_tmp_file
function read_value {
cat "${1}"
}
# Usage: set_value path_to_tmp_file the_value
function set_value {
echo "${2}" > "${1}"
}
#----
# Original code:
function test1() {
e=4
set_value "${tmp_file}" "${e}"
echo "hello"
}
# Create the temp file
# Note that tmp_file is available in test1 as well
tmp_file=$(mktemp)
# Your logic
e=2
# Store the value
set_value "${tmp_file}" "${e}"
# Run test1
test1
# Read the value modified by test1
e=$(read_value "${tmp_file}")
echo "$e"
The drawback is that you might need multiple temp files for different variables. And also you might need to issue a sync command to persist the contents on the disk between one write and read operations.
You can always use an alias:
alias next='printf "blah_%02d" $count;count=$((count+1))'
Related
Shell Scripting - How to mock some results based on an input?
I have a small scripts which verifies some conditions on a database server. I want to mock failures on all of those conditions to test the script, so I added the following line: ./print_results ${VAR1} ${VAR2} ... ${VARN} If any of the variables has a value different than ZERO it because it failed. so just for testing purpouses I added the line: VAR1=1 ; VAR2=1 ; ... ; VARN=1 But I need to edit the file every time I want to replace the real results with the fake ones. What's wrong with this? [! -z $1 ] && [ "$1" == "Y"] && { echo "Debugging is ACTIVE" ; VAR1=1 ; ... ; VAR2=1 ; } I want to have the VAR1..N = 1 after passing that line. Thanks.
The problem is that [ is a command, but [! is not. It is probably cleaner to write your code: test "{$1}" == Y && { echo "Debugging is ACTIVE"; VAR1=1 VAR2=1 ...; } No need for semi-colons between the variable assignments, but they don't hurt. This is one of the warts of sh. For some reason, it was thought to be a good idea to use the symbol [ for a command and pass it ] as an argument, trying to mimic braces in the language. Unfortunately, this leads to a great deal of confusion similar to that demonstrated in this question. It is far better to avoid [ completely and always spell it test. These two are functionally identical (except that the [ command must have ] as the final argument), and using test is much cleaner. (Would you expect test! to work?, or would you recognize that it needs to be written as ! test?)
Need a space between the "Y" and the ]. The non-zero test is pointless, but also requires a space between the [ and the !. [ "$1" == "Y" ] && { echo "Debugging is ACTIVE" ; VAR1=1 ; ... ; VAR2=1 ; } Also did you consider just writing this as an if...fi block?
bash provides a way to supply default values for parameters that aren't otherwise set. Presumably, your code has lines like VAR1=$1 VAR2=$2 VAR3=$3 Replace them with VAR1=${1-1} VAR2=${2-1} VAR3=${3-1} If $1 is unset, for instance, VAR1 will be assigned the value of 1 instead of the value of $1.
Shell Script that performs different functions based on input from file
I am trying to merge two very different scripts together for consolidation and ease of use purposes. I have an idea of how I want these scripts to look and operate, but I could use some help getting started. Here is the flow and look of the script: The input file would be a standard text file with this syntax: #Vegetables Broccoli|Green|14 Carrot|Orange|9 Tomato|Red|7 #Fruits Apple|Red|15 Banana|Yellow|5 Grape|Purple|10 The script would take the input of this file. It would ignore the commented portions, but use them to dictate the output. So based on the fact that it is a Vegetable, it would perform a specific function with the values listed between the delimiter (|). Then it would go to the Fruits and do something different with the values, based on that delimiter. Perhaps, I would add Vegetable/Fruit to one of the values and dependent on that value it would perform the function while in this loop to read the file. Thank you for your help in getting this started. UPDATE: So I am trying to implement the IFS setup and thought of a more logical arrangement. The input file will have the "categories" displayed within the parameters. So the setup will be like this: Vegetable|Carrot|Yellow Fruit|Apple|Red Vegetable|Tomato|Red From there, the script will read in the lines and perform the function. So basically this type of setup in shell: while read -r category item color do if [[ $category == "Vegetable" ]] ; then echo "The $item is $color" elif [[ $category == "Fruit" ]] ; then echo "The $item is $color" else echo "Bad input" done < "$input_file" Something along those lines...I am just having trouble putting it all together.
Use read to input the lines. Do a case statement on their prefix: { while read DATA; do case "$DATA" in \#*) ... switch function ...;; *) eval "$FUNCTION";; esac done } <inputfile Dependent on your problem you might want to experiment with setting $IFS before reading and read multiple variables in 1 go.
You can redefine the processing function each time you meet a # directive: #! /bin/bash while read line ; do if [[ $line == '#Vegetables' ]] ; then process () { echo Vegetables: "$#" } elif [[ $line == '#Fruits' ]] ; then process () { echo Fruits: "$#" } else process $line fi done < "$1" Note that the script does not skip empty lines.
ksh function return value in parentheses
In the following very simple ksh script example, I need to ask if func1 results equal to 4 , This is what I did in the example but this script does not print the "function result = 4" as I expected it to. What do I need to change in the [[......]] in order to print the "function result = 4" Remark - func1 must be in the [[.....]] #!/bin/ksh func1() { return 4 } [[ ` func1 ` = ` echo $? ` ]] && print "function result = 4"
You need #!/bin/ksh func1() { print -- 4 } [[ $(func1) = 4 ]] && print "function result = 4" OR #!/bin/ksh func1() { return 4 } func1 ; [[ $? == 4 ]] && print "function result = 4" There are several issues in the code that you present, so let me try to explain (You're making it more complicated than it need be). No. 1 is your use of back-ticks for command substitution, these have been deprecated in the ksh language since ~ 1995! Use $( ... cmd ) for modern cmd-substitution. We often see backticks listed as a nod to portability, but only scripts written for systems where the Bourne shell is the only shell available require the use of backticks. (well, I don't know about dash or ash, so maybe those too). No 2. is that $? gets set after ever function or command or pipeline is executed and is the return code of that last command. It is a value between 0-255. When you have code like cmd ; rc=$? ; echo $? ; you're now echoing the status of the assignment of rc=$? (which will almost always be 0), AND that is why you will see experienced scriptors save the value of $? before doing anything else with it. Recall that command-substitution uses what ever is the output of the $( ... cmd ...) or backtics enclosed command while return sets the value of $? (until the very next command execution resets that value). I hope this helps.
Function does return 4. The operator `` (backticks) ignores the result value, and returns the function's stdout instead (in your case an empty string, since func1 did not print anything to stdout). And `echo $?` is just over-complicated way of saying $?
How to return a string value from a Bash function
I'd like to return a string from a Bash function. I'll write the example in java to show what I'd like to do: public String getSomeString() { return "tadaa"; } String variable = getSomeString(); The example below works in bash, but is there a better way to do this? function getSomeString { echo "tadaa" } VARIABLE=$(getSomeString)
There is no better way I know of. Bash knows only status codes (integers) and strings written to the stdout.
You could have the function take a variable as the first arg and modify the variable with the string you want to return. #!/bin/bash set -x function pass_back_a_string() { eval "$1='foo bar rab oof'" } return_var='' pass_back_a_string return_var echo $return_var Prints "foo bar rab oof". Edit: added quoting in the appropriate place to allow whitespace in string to address #Luca Borrione's comment. Edit: As a demonstration, see the following program. This is a general-purpose solution: it even allows you to receive a string into a local variable. #!/bin/bash set -x function pass_back_a_string() { eval "$1='foo bar rab oof'" } return_var='' pass_back_a_string return_var echo $return_var function call_a_string_func() { local lvar='' pass_back_a_string lvar echo "lvar='$lvar' locally" } call_a_string_func echo "lvar='$lvar' globally" This prints: + return_var= + pass_back_a_string return_var + eval 'return_var='\''foo bar rab oof'\''' ++ return_var='foo bar rab oof' + echo foo bar rab oof foo bar rab oof + call_a_string_func + local lvar= + pass_back_a_string lvar + eval 'lvar='\''foo bar rab oof'\''' ++ lvar='foo bar rab oof' + echo 'lvar='\''foo bar rab oof'\'' locally' lvar='foo bar rab oof' locally + echo 'lvar='\'''\'' globally' lvar='' globally Edit: demonstrating that the original variable's value is available in the function, as was incorrectly criticized by #Xichen Li in a comment. #!/bin/bash set -x function pass_back_a_string() { eval "echo in pass_back_a_string, original $1 is \$$1" eval "$1='foo bar rab oof'" } return_var='original return_var' pass_back_a_string return_var echo $return_var function call_a_string_func() { local lvar='original lvar' pass_back_a_string lvar echo "lvar='$lvar' locally" } call_a_string_func echo "lvar='$lvar' globally" This gives output: + return_var='original return_var' + pass_back_a_string return_var + eval 'echo in pass_back_a_string, original return_var is $return_var' ++ echo in pass_back_a_string, original return_var is original return_var in pass_back_a_string, original return_var is original return_var + eval 'return_var='\''foo bar rab oof'\''' ++ return_var='foo bar rab oof' + echo foo bar rab oof foo bar rab oof + call_a_string_func + local 'lvar=original lvar' + pass_back_a_string lvar + eval 'echo in pass_back_a_string, original lvar is $lvar' ++ echo in pass_back_a_string, original lvar is original lvar in pass_back_a_string, original lvar is original lvar + eval 'lvar='\''foo bar rab oof'\''' ++ lvar='foo bar rab oof' + echo 'lvar='\''foo bar rab oof'\'' locally' lvar='foo bar rab oof' locally + echo 'lvar='\'''\'' globally' lvar='' globally
All answers above ignore what has been stated in the man page of bash. All variables declared inside a function will be shared with the calling environment. All variables declared local will not be shared. Example code #!/bin/bash f() { echo function starts local WillNotExists="It still does!" DoesNotExists="It still does!" echo function ends } echo $DoesNotExists #Should print empty line echo $WillNotExists #Should print empty line f #Call the function echo $DoesNotExists #Should print It still does! echo $WillNotExists #Should print empty line And output $ sh -x ./x.sh + echo + echo + f + echo function starts function starts + local 'WillNotExists=It still does!' + DoesNotExists='It still does!' + echo function ends function ends + echo It still 'does!' It still does! + echo Also under pdksh and ksh this script does the same!
Bash, since version 4.3, feb 2014(?), has explicit support for reference variables or name references (namerefs), beyond "eval", with the same beneficial performance and indirection effect, and which may be clearer in your scripts and also harder to "forget to 'eval' and have to fix this error": declare [-aAfFgilnrtux] [-p] [name[=value] ...] typeset [-aAfFgilnrtux] [-p] [name[=value] ...] Declare variables and/or give them attributes ... -n Give each name the nameref attribute, making it a name reference to another variable. That other variable is defined by the value of name. All references and assignments to name, except for⋅ changing the -n attribute itself, are performed on the variable referenced by name's value. The -n attribute cannot be applied to array variables. ... When used in a function, declare and typeset make each name local, as with the local command, unless the -g option is supplied... and also: PARAMETERS A variable can be assigned the nameref attribute using the -n option to the declare or local builtin commands (see the descriptions of declare and local below) to create a nameref, or a reference to another variable. This allows variables to be manipulated indirectly. Whenever the nameref variable is⋅ referenced or assigned to, the operation is actually performed on the variable specified by the nameref variable's value. A nameref is commonly used within shell functions to refer to a variable whose name is passed as an argument to⋅ the function. For instance, if a variable name is passed to a shell function as its first argument, running declare -n ref=$1 inside the function creates a nameref variable ref whose value is the variable name passed as the first argument. References and assignments to ref are treated as references and assignments to the variable whose name was passed as⋅ $1. If the control variable in a for loop has the nameref attribute, the list of words can be a list of shell variables, and a name reference will be⋅ established for each word in the list, in turn, when the loop is executed. Array variables cannot be given the -n attribute. However, nameref variables can reference array variables and subscripted array variables. Namerefs can be⋅ unset using the -n option to the unset builtin. Otherwise, if unset is executed with the name of a nameref variable as an argument, the variable referenced by⋅ the nameref variable will be unset. For example (EDIT 2: (thank you Ron) namespaced (prefixed) the function-internal variable name, to minimize external variable clashes, which should finally answer properly, the issue raised in the comments by Karsten): # $1 : string; your variable to contain the return value function return_a_string () { declare -n ret=$1 local MYLIB_return_a_string_message="The date is " MYLIB_return_a_string_message+=$(date) ret=$MYLIB_return_a_string_message } and testing this example: $ return_a_string result; echo $result The date is 20160817 Note that the bash "declare" builtin, when used in a function, makes the declared variable "local" by default, and "-n" can also be used with "local". I prefer to distinguish "important declare" variables from "boring local" variables, so using "declare" and "local" in this way acts as documentation. EDIT 1 - (Response to comment below by Karsten) - I cannot add comments below any more, but Karsten's comment got me thinking, so I did the following test which WORKS FINE, AFAICT - Karsten if you read this, please provide an exact set of test steps from the command line, showing the problem you assume exists, because these following steps work just fine: $ return_a_string ret; echo $ret The date is 20170104 (I ran this just now, after pasting the above function into a bash term - as you can see, the result works just fine.)
Like bstpierre above, I use and recommend the use of explicitly naming output variables: function some_func() # OUTVAR ARG1 { local _outvar=$1 local _result # Use some naming convention to avoid OUTVARs to clash ... some processing .... eval $_outvar=\$_result # Instead of just =$_result } Note the use of quoting the $. This will avoid interpreting content in $result as shell special characters. I have found that this is an order of magnitude faster than the result=$(some_func "arg1") idiom of capturing an echo. The speed difference seems even more notable using bash on MSYS where stdout capturing from function calls is almost catastrophic. It's ok to send in a local variables since locals are dynamically scoped in bash: function another_func() # ARG { local result some_func result "$1" echo result is $result }
You could also capture the function output: #!/bin/bash function getSomeString() { echo "tadaa!" } return_var=$(getSomeString) echo $return_var # Alternative syntax: return_var=`getSomeString` echo $return_var Looks weird, but is better than using global variables IMHO. Passing parameters works as usual, just put them inside the braces or backticks.
The most straightforward and robust solution is to use command substitution, as other people wrote: assign() { local x x="Test" echo "$x" } x=$(assign) # This assigns string "Test" to x The downside is performance as this requires a separate process. The other technique suggested in this topic, namely passing the name of a variable to assign to as an argument, has side effects, and I wouldn't recommend it in its basic form. The problem is that you will probably need some variables in the function to calculate the return value, and it may happen that the name of the variable intended to store the return value will interfere with one of them: assign() { local x x="Test" eval "$1=\$x" } assign y # This assigns string "Test" to y, as expected assign x # This will NOT assign anything to x in this scope # because the name "x" is declared as local inside the function You might, of course, not declare internal variables of the function as local, but you really should always do it as otherwise you may, on the other hand, accidentally overwrite an unrelated variable from the parent scope if there is one with the same name. One possible workaround is an explicit declaration of the passed variable as global: assign() { local x eval declare -g $1 x="Test" eval "$1=\$x" } If name "x" is passed as an argument, the second row of the function body will overwrite the previous local declaration. But the names themselves might still interfere, so if you intend to use the value previously stored in the passed variable prior to write the return value there, be aware that you must copy it into another local variable at the very beginning; otherwise the result will be unpredictable! Besides, this will only work in the most recent version of BASH, namely 4.2. More portable code might utilize explicit conditional constructs with the same effect: assign() { if [[ $1 != x ]]; then local x fi x="Test" eval "$1=\$x" } Perhaps the most elegant solution is just to reserve one global name for function return values and use it consistently in every function you write.
As previously mentioned, the "correct" way to return a string from a function is with command substitution. In the event that the function also needs to output to console (as #Mani mentions above), create a temporary fd in the beginning of the function and redirect to console. Close the temporary fd before returning your string. #!/bin/bash # file: func_return_test.sh returnString() { exec 3>&1 >/dev/tty local s=$1 s=${s:="some default string"} echo "writing directly to console" exec 3>&- echo "$s" } my_string=$(returnString "$*") echo "my_string: [$my_string]" executing script with no params produces... # ./func_return_test.sh writing directly to console my_string: [some default string] hope this helps people -Andy
You could use a global variable: declare globalvar='some string' string () { eval "$1='some other string'" } # ---------- end of function string ---------- string globalvar echo "'${globalvar}'" This gives 'some other string'
To illustrate my comment on Andy's answer, with additional file descriptor manipulation to avoid use of /dev/tty: #!/bin/bash exec 3>&1 returnString() { exec 4>&1 >&3 local s=$1 s=${s:="some default string"} echo "writing to stdout" echo "writing to stderr" >&2 exec >&4- echo "$s" } my_string=$(returnString "$*") echo "my_string: [$my_string]" Still nasty, though.
The way you have it is the only way to do this without breaking scope. Bash doesn't have a concept of return types, just exit codes and file descriptors (stdin/out/err, etc)
Addressing Vicky Ronnen's head up, considering the following code: function use_global { eval "$1='changed using a global var'" } function capture_output { echo "always changed" } function test_inside_a_func { local _myvar='local starting value' echo "3. $_myvar" use_global '_myvar' echo "4. $_myvar" _myvar=$( capture_output ) echo "5. $_myvar" } function only_difference { local _myvar='local starting value' echo "7. $_myvar" local use_global '_myvar' echo "8. $_myvar" local _myvar=$( capture_output ) echo "9. $_myvar" } declare myvar='global starting value' echo "0. $myvar" use_global 'myvar' echo "1. $myvar" myvar=$( capture_output ) echo "2. $myvar" test_inside_a_func echo "6. $_myvar" # this was local inside the above function only_difference will give 0. global starting value 1. changed using a global var 2. always changed 3. local starting value 4. changed using a global var 5. always changed 6. 7. local starting value 8. local starting value 9. always changed Maybe the normal scenario is to use the syntax used in the test_inside_a_func function, thus you can use both methods in the majority of cases, although capturing the output is the safer method always working in any situation, mimicking the returning value from a function that you can find in other languages, as Vicky Ronnen correctly pointed out.
The options have been all enumerated, I think. Choosing one may come down to a matter of the best style for your particular application, and in that vein, I want to offer one particular style I've found useful. In bash, variables and functions are not in the same namespace. So, treating the variable of the same name as the value of the function is a convention that I find minimizes name clashes and enhances readability, if I apply it rigorously. An example from real life: UnGetChar= function GetChar() { # assume failure GetChar= # if someone previously "ungot" a char if ! [ -z "$UnGetChar" ]; then GetChar="$UnGetChar" UnGetChar= return 0 # success # else, if not at EOF elif IFS= read -N1 GetChar ; then return 0 # success else return 1 # EOF fi } function UnGetChar(){ UnGetChar="$1" } And, an example of using such functions: function GetToken() { # assume failure GetToken= # if at end of file if ! GetChar; then return 1 # EOF # if start of comment elif [[ "$GetChar" == "#" ]]; then while [[ "$GetChar" != $'\n' ]]; do GetToken+="$GetChar" GetChar done UnGetChar "$GetChar" # if start of quoted string elif [ "$GetChar" == '"' ]; then # ... et cetera As you can see, the return status is there for you to use when you need it, or ignore if you don't. The "returned" variable can likewise be used or ignored, but of course only after the function is invoked. Of course, this is only a convention. You are free to fail to set the associated value before returning (hence my convention of always nulling it at the start of the function) or to trample its value by calling the function again (possibly indirectly). Still, it's a convention I find very useful if I find myself making heavy use of bash functions. As opposed to the sentiment that this is a sign one should e.g. "move to perl", my philosophy is that conventions are always important for managing the complexity of any language whatsoever.
In my programs, by convention, this is what the pre-existing $REPLY variable is for, which read uses for that exact purpose. function getSomeString { REPLY="tadaa" } getSomeString echo $REPLY This echoes tadaa But to avoid conflicts, any other global variable will do. declare result function getSomeString { result="tadaa" } getSomeString echo $result If that isn’t enough, I recommend Markarian451’s solution.
They key problem of any 'named output variable' scheme where the caller can pass in the variable name (whether using eval or declare -n) is inadvertent aliasing, i.e. name clashes: From an encapsulation point of view, it's awful to not be able to add or rename a local variable in a function without checking ALL the function's callers first to make sure they're not wanting to pass that same name as the output parameter. (Or in the other direction, I don't want to have to read the source of the function I'm calling just to make sure the output parameter I intend to use is not a local in that function.) The only way around that is to use a single dedicated output variable like REPLY (as suggested by Evi1M4chine) or a convention like the one suggested by Ron Burk. However, it's possible to have functions use a fixed output variable internally, and then add some sugar over the top to hide this fact from the caller, as I've done with the call function in the following example. Consider this a proof of concept, but the key points are The function always assigns the return value to REPLY, and can also return an exit code as usual From the perspective of the caller, the return value can be assigned to any variable (local or global) including REPLY (see the wrapper example). The exit code of the function is passed through, so using them in e.g. an if or while or similar constructs works as expected. Syntactically the function call is still a single simple statement. The reason this works is because the call function itself has no locals and uses no variables other than REPLY, avoiding any potential for name clashes. At the point where the caller-defined output variable name is assigned, we're effectively in the caller's scope (technically in the identical scope of the call function), rather than in the scope of the function being called. #!/bin/bash function call() { # var=func [args ...] REPLY=; "${1#*=}" "${#:2}"; eval "${1%%=*}=\$REPLY; return $?" } function greet() { case "$1" in us) REPLY="hello";; nz) REPLY="kia ora";; *) return 123;; esac } function wrapper() { call REPLY=greet "$#" } function main() { local a b c d call a=greet us echo "a='$a' ($?)" call b=greet nz echo "b='$b' ($?)" call c=greet de echo "c='$c' ($?)" call d=wrapper us echo "d='$d' ($?)" } main Output: a='hello' (0) b='kia ora' (0) c='' (123) d='hello' (0)
You can echo a string, but catch it by piping (|) the function to something else. You can do it with expr, though ShellCheck reports this usage as deprecated.
bash pattern to return both scalar and array value objects: definition url_parse() { # parse 'url' into: 'url_host', 'url_port', ... local "$#" # inject caller 'url' argument in local scope local url_host="..." url_path="..." # calculate 'url_*' components declare -p ${!url_*} # return only 'url_*' object fields to the caller } invocation main() { # invoke url parser and inject 'url_*' results in local scope eval "$(url_parse url=http://host/path)" # parse 'url' echo "host=$url_host path=$url_path" # use 'url_*' components }
Although there were a lot of good answers, they all did not work the way I wanted them to. So here is my solution with these key points: Helping the forgetful programmer Atleast I would struggle to always remember error checking after something like this: var=$(myFunction) Allows assigning values with newline chars \n Some solutions do not allow for that as some forgot about the single quotes around the value to assign. Right way: eval "${returnVariable}='${value}'" or even better: see the next point below. Using printf instead of eval Just try using something like this myFunction "date && var2" to some of the supposed solutions here. eval will execute whatever is given to it. I only want to assign values so I use printf -v "${returnVariable}" "%s" "${value}" instead. Encapsulation and protection against variable name collision If a different user or at least someone with less knowledge about the function (this is likely me in some months time) is using myFunction I do not want them to know that he must use a global return value name or some variable names are forbidden to use. That is why I added a name check at the top of myFunction: if [[ "${1}" = "returnVariable" ]]; then echo "Cannot give the ouput to \"returnVariable\" as a variable with the same name is used in myFunction()!" echo "If that is still what you want to do please do that outside of myFunction()!" return 1 fi Note this could also be put into a function itself if you have to check a lot of variables. If I still want to use the same name (here: returnVariable) I just create a buffer variable, give that to myFunction and then copy the value returnVariable. So here it is: myFunction(): myFunction() { if [[ "${1}" = "returnVariable" ]]; then echo "Cannot give the ouput to \"returnVariable\" as a variable with the same name is used in myFunction()!" echo "If that is still what you want to do please do that outside of myFunction()!" return 1 fi if [[ "${1}" = "value" ]]; then echo "Cannot give the ouput to \"value\" as a variable with the same name is used in myFunction()!" echo "If that is still what you want to do please do that outside of myFunction()!" return 1 fi local returnVariable="${1}" local value=$'===========\nHello World\n===========' echo "setting the returnVariable now..." printf -v "${returnVariable}" "%s" "${value}" } Test cases: var1="I'm not greeting!" myFunction var1 [[ $? -eq 0 ]] && echo "myFunction(): SUCCESS" || echo "myFunction(): FAILURE" printf "var1:\n%s\n" "${var1}" # Output: # setting the returnVariable now... # myFunction(): SUCCESS # var1: # =========== # Hello World # =========== returnVariable="I'm not greeting!" myFunction returnVariable [[ $? -eq 0 ]] && echo "myFunction(): SUCCESS" || echo "myFunction(): FAILURE" printf "returnVariable:\n%s\n" "${returnVariable}" # Output # Cannot give the ouput to "returnVariable" as a variable with the same name is used in myFunction()! # If that is still what you want to do please do that outside of myFunction()! # myFunction(): FAILURE # returnVariable: # I'm not greeting! var2="I'm not greeting!" myFunction "date && var2" [[ $? -eq 0 ]] && echo "myFunction(): SUCCESS" || echo "myFunction(): FAILURE" printf "var2:\n%s\n" "${var2}" # Output # setting the returnVariable now... # ...myFunction: line ..: printf: `date && var2': not a valid identifier # myFunction(): FAILURE # var2: # I'm not greeting! myFunction var3 [[ $? -eq 0 ]] && echo "myFunction(): SUCCESS" || echo "myFunction(): FAILURE" printf "var3:\n%s\n" "${var3}" # Output # setting the returnVariable now... # myFunction(): SUCCESS # var3: # =========== # Hello World # ===========
#Implement a generic return stack for functions: STACK=() push() { STACK+=( "${1}" ) } pop() { export $1="${STACK[${#STACK[#]}-1]}" unset 'STACK[${#STACK[#]}-1]'; } #Usage: my_func() { push "Hello world!" push "Hello world2!" } my_func ; pop MESSAGE2 ; pop MESSAGE1 echo ${MESSAGE1} ${MESSAGE2}
agt#agtsoft:~/temp$ cat ./fc #!/bin/sh fcall='function fcall { local res p=$1; shift; fname $*; eval "$p=$res"; }; fcall' function f1 { res=$[($1+$2)*2]; } function f2 { local a; eval ${fcall//fname/f1} a 2 3; echo f2:$a; } a=3; f2; echo after:a=$a, res=$res agt#agtsoft:~/temp$ ./fc f2:10 after:a=3, res=
Get a list of function names in a shell script [duplicate]
This question already has answers here: How do I list the functions defined in my shell? [duplicate] (8 answers) Closed 4 years ago. I have a Bourne Shell script that has several functions in it, and allows to be called in the following way: my.sh <func_name> <param1> <param2> Inside, func_name() will be called with param1 and param2. I want to create a help function that would just list all available functions, even without parameters. The question: how do I get a list of all function names in a script from inside the script? I'd like to avoid having to parse it and look for function patterns. Too easy to get wrong. Update: the code. Wanted my help() function be like main() - a function added to the code is added to the help automatically. #!/bin/sh # must work with "set -e" foo () { echo foo: -$1-$2-$3- return 0 } # only runs if there are parameters # exits main () { local cmd="$1" shift local rc=0 $cmd "$#" || rc=$? exit $rc } if [[ "$*" ]] then main "$#" die "how did we get here?" fi
You can get a list of functions in your script by using the grep command on your own script. In order for this approach to work, you will need to structure your functions a certain way so grep can find them. Here is a sample: $ cat my.sh #!/bin/sh function func1() # Short description { echo func1 parameters: $1 $2 } function func2() # Short description { echo func2 parameters: $1 $2 } function help() # Show a list of functions { grep "^function" $0 } if [ "_$1" = "_" ]; then help else "$#" fi Here is an interactive demo: $ my.sh function func1() # Short description function func2() # Short description function help() # Show a list of functions $ my.sh help function func1() # Short description function func2() # Short description function help() # Show a list of functions $ my.sh func1 a b func1 parameters: a b $ my.sh func2 x y func2 parameters: x y If you have "private" function that you don't want to show up in the help, then omit the "function" part: my_private_function() { # Do something }
typeset -f returns the functions with their bodies, so a simple awk script is used to pluck out the function names f1 () { :; } f2 () { :; } f3 () { :; } f4 () { :; } help () { echo "functions available:" typeset -f | awk '/ \(\) $/ && !/^main / {print $1}' } main () { help; } main This script outputs: functions available: f1 f2 f3 f4 help
You call this function with no arguments and it spits out a "whitespace" separated list of function names only. function script.functions () { local fncs=`declare -F -p | cut -d " " -f 3`; # Get function list echo $fncs; # not quoted here to create shell "argument list" of funcs. } To load the functions into an array: declare MyVar=($(script.functions)); Of course, common sense dictates that any functions that haven't been sourced into the current file before this is called will not show up in the list. To Make the list read-only and available for export to other scripts called by this script: declare -rx MyVar=($(script.functions)); To print the entire list as newline separated: printf "%s\n" "${MyVar[#]}";
The best thing to do is make an array (you are using bash) that contains functions that you want to advertise and have your help function iterate over and print them. Calling set alone will produce the functions, but in their entirety. You'd still have to parse that looking for things ending in () to get the proverbial symbols. Its also probably saner to use something like getopt to turn --function-name into function_name with arguments. But, well, sane is relative and you have not posted code :) Your other option is to create a loadable for bash (a fork of set) that accomplishes this. Honestly, I'd prefer going with parsing before writing a loadable for this task.