• Uncategorized

About bash : Set-a-parent-shells-variable-from-a-subshell

Question Detail

How do I set a variable in the parent shell, from a subshell?

a=3
(a=4)
echo $a

Question Answer

The whole point of a subshell is that it doesn’t affect the calling session. In bash a subshell is a child process, other shells differ but even then a variable setting in a subshell does not affect the caller. By definition.

Do you need a subshell? If you just need a group then use braces:

a=3
{ a=4;}
echo $a

gives 4 (be careful of the spaces in that one). Alternatively, write the variable value to stdout and capture it in the caller:

a=3
a=$(a=4;echo $a)
echo $a

avoid using back-ticks “, they are deprecated and can be difficult to read.
……………………………………………………
There is the gdb-bash-variable hack:

gdb –batch-silent -ex “attach $$” -ex ‘set bind_variable(“a”, “4”, 0)’;

although that always sets a variable in the global scope, not just the parent scope
……………………………………………………
You don’t. The subshell doesn’t have access to its parent’s environment. (At least within the abstraction that Bash provides. You could potentially try to use gdb, or smash the stack, or whatnot, to gain such access clandestinely. I wouldn’t recommend that, though.)

One alternative is for the subshell to write assignment statements to a temporary file for its parent to read:

a=3
(echo ‘a=4’ > tmp)
. tmp
rm tmp
echo “$a”

……………………………………………………
If the problem is related to a while loop, one way to fix this is by using Process Substitution:

var=0
while read i;
do
# perform computations on $i
((var++))
done < <(find . -type f -name "*.bin" -maxdepth 1) as shown here: https://stackoverflow.com/a/13727116/2547445 ............................................................ To change variables in a script called from a parent script, you can call the script preceded with a "." a=3 echo $a . ./calledScript.sh echo $a in calledScript.sh a=4 Expected output 3 4 ............................................................ By reading the answer from @ruakh (thank you) with a temporary file approach and the comments asking for a file descriptors solution, I got the following idea: a=3 . <(echo a=4; echo b=5) echo $a echo $b It allows returning different variables at once (which could be an issue in the subshell variant of the accepted answer). No iteration is needed, No temporary file to take care of. Close to the syntax proposed by the OP. Result: 4 5 With xtrace enabled is visible that we are sourcing from the file descriptor created for the output of the subshell: + a=3 + . /dev/fd/63 # <-- the file descriptor 😉 ++ echo a=4 ++ echo b=5 ++ a=4 ++ b=5 + echo 4 4 + echo 5 5 ............................................................ You can output the value in the subshell and assign the subshell output to a variable in the caller script: # subshell.sh echo Value # caller myvar=$(subshell.sh) If the subshell has more to output you can separate the variable value and other messages by redirecting them into different output streams: # subshell.sh echo "Writing value" 1>&2
echo Value

# caller
myvar=$(subshell.sh 2>/dev/null) # or to somewhere else
echo $myvar

Alternatively, you can output variable assignments in the subshell, evaluate them in the caller script and avoid using files to exchange information:

# subshell.sh
echo “a=4”

# caller
# export $(subshell.sh) would be more secure, since export accepts name=value only.
eval $(subshell.sh)
echo $a

The last way I can think of is to use exit codes but this covers the integer values exchange only (and in a limited range) and breaks the convention for interpreting exit codes (0 for success non-0 for everything else).
……………………………………………………
Instead of accessing the variable from the parent shell, change the order of the commands and use the process substitution:
a=3
echo 5 | (read a)
echo $a

prints 3
a=3
read a < <(echo 5) echo $a prints 5 Another example: let i=0 seq $RANDOM | while read r do let i=r done echo $i vs let i=0 while read r do let i=r done < <(seq $RANDOM) echo $i Alternatively, when job control is inactive (e.g. in scripts) you can use the lastpipe shell option to achieve the same result without changing the order of the commands: #!/bin/bash shopt -s lastpipe let i=0 seq $RANDOM | while read r do let i=r done echo $i ............................................................ Unless you can apply all io to pipes and use file handles, basic variable updating is impossible within $(command) and any other sub-process. Regular files, however, are bash's global variables for normal sequential processing. Note: Due to race conditions, this simple approach is not good for parallel processing. Create an set/get/default function like this: globalVariable() { # NEW-VALUE # set/get/default globalVariable if [ 0 = "$#" ]; then # new value not given -- echo the value [ -e "$aRam/globalVariable" ] \ && cat "$aRam/globalVariable" \ || printf "default-value-here" else # new value given -- set the value printf "%s" "$1" > “$aRam/globalVariable”
fi
}

“$aRam” is the directory where values are stored. I like it to be a ram disk for speed and volatility:

aRam=”$(mktemp -td $(basename “$0″).XXX)” # temporary directory
mount -t tmpfs ramdisk “$aRam” # mount the ram disk there
trap “umount “$aRam” && rm -rf “$aRam”” EXIT # auto-eject

To read the value:

v=”$(globalVariable)” # or part of any command

To set the value:

globalVariable newValue # newValue will be written to file

To unset the value:

rm -f “$aRam/globalVariable”

The only real reason for the access function is to apply a default value because cat will error given a non-existent file. It is also useful to apply other get/set logic. Otherwise, it would not be needed at all.

An ugly read method avoiding cat’s non-existent file error:

v=”$(cat “$aRam/globalVariable 2>/dev/null”)”

A cool feature of this mess is that you can open another terminal and examine the contents of the files while the program is running.
……………………………………………………
While it’s harder to get multiple variables out of a subshell, you can set multiple variables inside a function without using globals.
You can pass the name of a variable into a function that uses local -n to turn it into a special variable called a nameref:
myfunc() {
local -n OUT=$1
local -n SIDEEFFECT=$2
OUT=’foo’
SIDEEFFECT=’bar’
}

myfunc A B
echo $A
> foo
echo $B
> bar

This is the technique I ended up using instead of getting subshell FOO=$(myfunc) working setting multiple variables.
……………………………………………………
A very simple and practical method that allows multiple variables is as follows, eventually may add parameters to the call:
function ComplexReturn(){
# do your processing…
a=123
b=456
echo -n “AAA=${a}; BBB=${b};”
}
# … this can be internal function or any subshell command
eval $(ComplexReturn)
echo $AAA $BBB

You may also like...

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.