How to make a variable from a subshell available in the parent shell
Solution 1
You can't bring a variable's value from a subshell to its parent, not without doing some error-prone marshalling and cumbersome communication.
Fortunately, you don't need a subshell here. Redirection only requires command grouping with { … }
, not a subshell.
{ time -p response=$(curl --write-out '%{http_code}' --silent -O "${BASE_URL}/${report_code}"); } 2> "${report_code}.time"
(Don't forget double quotes around variable substitutions.)
Solution 2
Fellow U&L users: Before downvoting my answer for using C-style with main()
function, please visit this link: https://unix.stackexchange.com/a/313561/85039 Using main functions in scripts is a common practice, used by many professionals in the field.
As Gilles pointed out, subshells cannot make variables available outside of their environment. But let's approach this problem from another angle - if you write your script in functions, it's possible to declare variable as local
and that can be edited.
From bash 4.3's manual, local
description:
...When local is used within a function, it causes the variable name to have a visible scope restricted to that function and its children...
Example:
#!/bin/bash
outter()
{
for i in $(seq 1 3)
do
var=$i
done
}
main()
{
local var=0
outter
echo $var
}
main "$@"
$ ./testscript.sh
3
As you can see after 3 iterations of the looping function, the variable is modified.
Related videos on Youtube
iconoclast
Contractor at Infinite Red, a mobile app & web site design & development company with employees worldwide, experts in React Native, Rails, Phoenix, and all things JavaScript!
Updated on September 18, 2022Comments
-
iconoclast over 1 year
I've written a quick-and-dirty script to time some reports from a web service:
BASE_URL='http://example.com/json/webservice/' FIRST=1 FINAL=10000 for report_code in $(seq 1 $FINAL); do (time -p response=$(curl --write-out %{http_code} --silent -O ${BASE_URL}/${report_code}) ) 2> ${report_code}.time echo $response # <------- this is out of scope! How do I fix that? if [[ $response = '404' ]]; then echo "Deleting report # ${report_code}!" rm ${report_code} else echo "${report_code} seems to be good!" fi done
I need to wrap the
time
command in a subshell so I can redirect its output, but that makes the value of$response
unavailable to the parent shell. How do I get around this problem? -
To Kra over 7 yearsits funny that every programming language can do that. i already spent 1h googling with no possible solution in shell. im disapointed.
-
Gilles 'SO- stop being evil' over 7 years@ToKra No. No programming language can do this. (Almost) any programming language can bring a variable's value from a subroutine or instruction block to its parent, and that's precisely what I explain in my answer: use command grouping
{ … }
instead of a subshell( … )
. -
Ruslan almost 7 yearsHmm, unintuitive behavior for C programmers. I'd normally expect
outter
to refer to a global instance ofvar
. -
Golar Ramblar almost 6 yearsThis answer has some flaws.
"$var"
is not needed in the call outoutter
. It does not do anything. And alsolocal var=0
does not do anything; the call oufoutter
does overwritevar
, as you stated. -
Sergiy Kolodyazhnyy almost 6 years@GolarRamblar I've removed
"$var"
as positional argument tooutter
; to be fair, this is out of a habit. Can you elaborate as tolocal var=0
part ? -
ATLief over 2 yearsNote that the final statement needs the semicolon included in this answer if it is on the same line as the closing brace. Otherwise you'll get a syntax error.
-
Admin almost 2 yearsA race condition is introduced by the use of the ampersand ("&") here. Other than causing
testfn
to run in a subshell, it also causestestfn
to run in the background asynchronously. This problem does not manifest here due toecho
's speed, but consider changingecho
to acurl
command like OP or adding a sleep in front ofecho
. Potential results include "test.txt: No such file or directory" or an empty string. To resolve, keep the parentheses like OP (e.g.,(testfn)
) or add await
aftertestfn &
but beforetestresult=[...]