16

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?

4 Answers 4

15

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.)

3
  • its funny that every programming language can do that. i already spent 1h googling with no possible solution in shell. im disapointed. Commented Dec 28, 2016 at 17:49
  • 1
    @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 ( … ). Commented Dec 28, 2016 at 17:51
  • 1
    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. Commented Aug 9, 2021 at 23:06
4

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.

0
1

As of bash 4.3 you can do this with nameref variables

#!/usr/bin/env bash

# outvar is a nameref variable that allows us to set the value of a variable 
# outside our scope
addfoo() {
   local x="$1"
   local -n outvar=$2

   x="foo$x"

   outvar="$x"
}

declare b=“loll”

addfoo "bar" b

echo "$b”

This will output foobar as the function addfoo modifies the b variable, even though b is outside its scope.

Namerefs are useful for extracting values from a function without using a sub shell $(addfoo).

0

You can redirect your variable in a test.txt and get it in the parent shell.

testfn()
{
echo "test" > test.txt # redirect your variable in a test.txt
}

testfn &       # execute your function in the subshell

testresult=$(cat test.txt) # get your variable in the parent shell
printf '%s\n' "$testresult"
1
  • 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=[...] Commented Jun 15, 2022 at 4:48

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.