How to make a variable from a subshell available in the parent shell

9,999

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.

Share:
9,999

Related videos on Youtube

iconoclast
Author by

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, 2022

Comments

  • iconoclast
    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
    To Kra over 7 years
    its 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'
    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
    Ruslan almost 7 years
    Hmm, unintuitive behavior for C programmers. I'd normally expect outter to refer to a global instance of var.
  • Golar Ramblar
    Golar Ramblar almost 6 years
    This answer has some flaws. "$var" is not needed in the call out outter. It does not do anything. And also local var=0 does not do anything; the call ouf outter does overwrite var, as you stated.
  • Sergiy Kolodyazhnyy
    Sergiy Kolodyazhnyy almost 6 years
    @GolarRamblar I've removed "$var" as positional argument to outter; to be fair, this is out of a habit. Can you elaborate as to local var=0 part ?
  • ATLief
    ATLief over 2 years
    Note 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
    Admin almost 2 years
    A race condition is introduced by the use of the ampersand ("&") here. Other than causing testfn to run in a subshell, it also causes testfn to run in the background asynchronously. This problem does not manifest here due to echo's speed, but consider changing echo to a curl command like OP or adding a sleep in front of echo. 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 a wait after testfn & but before testresult=[...]