37

(In BASH) I want a subshell to use a non-STDOUT non-STDERR file descriptor to pass some data back to the parent shell. How can I do that? Eventually I would love to save the data into some variable of the parent shell.

(
  # The following two lines show the behavior of the subshell.
  # We cannot change them.
  echo "This should go to STDOUT"
  echo "This is the data I want to pass to the parent shell" >&3
)
#...
data_from_subshell=... # Somehow assign the value of &3 of the
                       # subshell to this variable

EDIT: The subshell runs a black-box program that writes to STDOUT and &3.

Suvarna Pattayil
  • 5,136
  • 5
  • 32
  • 59
user716468
  • 1,513
  • 2
  • 13
  • 20

2 Answers2

34

BEWARE, BASHISM AHEAD (there are posix shells that are significantly faster than bash, e.g. ash or dash, that don't have process substitution).

You can do a handle dance to move original standard output to a new descriptor to make standard output available for piping (from the top of my head):

exec 3>&1 # open 3 to the same output as 1
run_in_subshell() { # just shortcut for the two cases below
    echo "This goes to STDOUT" >&3
    echo "And this goes to THE OTHER FUNCTION"
}

Now you should be able to write:

while read line; do
    process $line
done < <(run_in_subshell)

but the <() construct is a bashism. You can replace it with pipeline

run_in_subshell | while read line; do
    process $line
done

except than the second command also runs in subshell, because all commands in pipeline do.

Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
  • Your solution is very inspiring! I think it can pretty much do it. I just submitted an edit to this solution. With a swapping of &1 and &3 outside the subshell we can avoid changing the original subshell. However I am not sure if your second solution (with | ) can work because the while loop after the pipe is also a subshell in BASH. – user716468 Mar 01 '13 at 11:00
  • @user716468: You'd have to swap &3 and &1 inside the subshell, because both pipe and process substitution are only able to take &1. Yes, in the later the loop runs in a subshell too. That's unfortunately all you can get out of non-bash. It's a bit of pain, but since the parent and child are mostly equivalent, often you can just move the rest of the script into the receiving command in the pipeline. – Jan Hudec Mar 01 '13 at 11:54
  • Of course the bash solution works in other shells like _zsh_ too. But it does not work in the simpler shells often used as `/bin/sh` for performance reasons. – Jan Hudec Mar 01 '13 at 11:56
  • 1
    bashism disclaimer very helpful! – Roy Truelove Feb 23 '16 at 18:09
  • 1
    "do a handle dance" -- I feel like that's what I do to open my car door :D love it – Peter M. Elias Aug 23 '19 at 15:13
6

The easiest way of course, is to capture the output directly in the parent

data_from_subshell=$(echo "This is the data I want to pass to the parent shell")

You can use a named pipe as an alternative way to read data from a child

mkfifo /tmp/fifo

now you can redirect the child to /tmp/fifo

(
    echo "This should go to STDOUT"
    echo "This is the data I want to pass to the parent shell" >/tmp/fifo
) &

and the parent can read from there

read data_from_subshell </tmp/fifo

Another way is to use coproc to start a child process. This creates a child with a bidirectional pipe and redirects the child's stdin and stdout to the pipe descriptors. To use both the pipe and stdout in the child, you must duplicate stdout in the parent first

exec 4>&1 # duplicate stdout for usage in client

coproc SUBSHELL (
    exec 3>&1 1>&4- # redirect fd 3 to pipe, redirect fd 1 to stdout
    (
    echo "This should go to STDOUT"
    echo "This is the data I want to pass to the parent shell" >&3
    )
)

exec 4>&- # close fd 4 in parent
read data <&${SUBSHELL[0]}
echo "Parent: $data"

Coprocesses were introduced in Bash 4.0.

Olaf Dietsche
  • 72,253
  • 8
  • 102
  • 198
  • 1
    A variable that captures the data in a subshell is not visible to the parent shell. – user716468 Mar 01 '13 at 09:30
  • When using named pipes the subshell has to be run concurrently with the parent shell (using &), otherwise it will block at ">/tmp/fifo". More synchronization is required in this case. – user716468 Mar 01 '13 at 09:32
  • @user716468 I never meant to capture the data in the subshell, of course. I clarified my answer. If you already know the answer to your question, why are you asking in the first place? – Olaf Dietsche Mar 01 '13 at 09:34
  • Thanks for your answers and clarification. No I have not found an answer yet. The variable way may not work since I want the subshell to use STDOUT to print something else, while at the same time pass some data to the parent. – user716468 Mar 01 '13 at 09:52
  • Shouldn't process substitution be enough here? – Jan Hudec Mar 01 '13 at 10:07
  • @JanHudec It's effectively the same, just a different syntax. A child process writing to both stdout and a pipe. – Olaf Dietsche Mar 01 '13 at 10:14
  • @user716468 It takes some fiddling with file descriptors, but you can work things out so that it fits. Please see modified answer. – Olaf Dietsche Mar 01 '13 at 10:58
  • @user4838443 Thank you, I added your hint about the Bash version to my answer. – Olaf Dietsche May 05 '15 at 15:53