How to export a variable from a subshell to the parent shell?

A not-too-ugly solution to this classic problem...

#!/bin/bash

mknod fifo p
 
(
       echo 'value' > fifo &
)
 
VARIABLE=`cat fifo`
rm fifo

Whatdcha think? Good? No good?

What happens if you have more than 1 instance if this script running?

I would suggest generating a temp filename for the pipe and passing it to the child.

Excellent point. Then, maybe something like:

#!/bin/bash

TMPFIFO="/tmp/fifo.$$"
 
mknod $TMPFIFO p
 
(
       echo 'value' > $TMPFIFO &
)
 
VARIABLE=`cat $TMPFIFO`
rm $TMPFIFO

Thank you.

Yes or

#!/bin/bash

PIPE="/tmp/pipe.$$"
mkfifo $PIPE

(
echo 'value' > $PIPE &
)

VARIABLE=`cat $PIPE`
echo $VARIABLE
rm $PIPE
#!/bin/bash

TMPF="/tmp/tmpf.$$"
mktemp $TMPF >/dev/null

(
echo 'value' > $TMPF
)

VARIABLE=`cat $TMPF`
echo $VARIABLE
rm $TMPF

I would use mkfifo instead of mknod. You do not need the ( ) in this case.. If you have more than one write statement, it is better to use exec, otherwise VARIABLE=`cat $TMPFIFO` goes on after the first command writes an EOF and the sub process will not finish.

FIFO="/tmp/fifo.$$"
if ! [ -p "$FIFO" ]; then
  mkfifo "$FIFO"
fi
{ exec >"$FIFO"; sleep 2; ps -f ;echo "hello from $FIFO" ;} &
var=$(< "$FIFO")
rm "$FIFO"
echo "$var"

In ksh93 you can use coprocesses:

#!/bin/ksh
{ sleep 2; ps -f ;echo "hello from coproc" ;} |&
var=$(<&p)
echo "$var"

---------- Post updated at 11:44 ---------- Previous update was at 10:45 ----------

bash 4:

#!/bin/bash
coproc TEST { sleep 2; ps -f ;echo "hello from coproc" ;}
var=$(cat <&${TEST[0]})
echo "$var"

I agree with your mkfifo sugestion, but in regards to my usage of (), let me explain:

Suppose you want to extract a fixed amount of data, say, 20 bytes, from a device file which spits data out in asynchronous patterns, such as /dev/mouse.

The device file/dev/mouse will sometimes be spitting bytes out, and will sometimes not, depending on how the user moves the cursor around, so you really can't know when exactly you're going to get your 20 bytes from it.

So what do you do? You run dd to get 20 bytes from /dev/mouse, like so:

dd if=/dev/mouse bs=1 count=20

But this ofcourse freezes the execution of your script until /dev/mouse has produced the 20 required bytes. What do you do then?, well, you run it in the background:

dd if=/dev/mouse bs=1 count=20 &

But then, this only allows you to output the 20 bytes to stdout and, where's the fun in that? If you wanted to manipulate the 20 bytes in some way or another, you would need to either:

  1. Store them in a binary temp file, or
  2. Store them in a variable

The disadvantage of using binary temp files is that you waste slow HDD cycles and it just makes your code look plain ugly. So we decide to go with variable storage: it uses RAM instead, it's non intrusive with your filesystem and it doesn't carry all the cumbersome complications of checking for occupied filenames and the such. Thus, we unsuspectingly attempt to:

VAR=`dd if=/dev/mouse bs=1 count=20` &

Which obviously fails, because & runs your background processes in a child shell, which cannot alter it's parent's environment. At this point you can either give up and use the ugly temp.bin alternative:

dd if=/dev/mouse bs=1 count=20 of=/tmp/temp.bin.$$ &

Or...

 3. Communicate with the parent from your background process using a fifo.

Like so:

TMPFIFO="/tmp/fifo.$$"
 
mkfifo $TMPFIFO
 
dd if=/dev/mouse bs=1 count=20 of=$TMPFIFO &

echo 'I can do something right after I tell dd to start listening'
echo 'to /dev/mouse, like telling my robotic arm to move the'
echo 'mouse around, or whatever :P!'
  
# This part WILL freeze the scrip though, at least
# until /dev/mouse is done with the 20 bytes.
VARIABLE=`cat $TMPFIFO`
rm $TMPFIFO

echo "Use \$VARIABLE for something... etc, etc: $VARIABLE"

This doesn't mess (so much) with your filesystem, uses only RAM, and is faster. I admit it shares with 1. the flaw of having to check for filenames... I only wish it could be perfect...

Anyway, that is why I needed my children talking back at their parents.

Thank you for your criticism, though, I truly appreciate your taking the time to read my post.

---------- Post updated at 01:06 PM ---------- Previous update was at 12:35 PM ----------

mktemp huh... never heard of it, thanks for the tip!

@vomv1988: Thank you for the explanation. You started an interesting thread, I would not say it is criticism, but rather some hopefully helpful comments. Regarding the parentheses in your original script, I was only referring to them because I think they are superfluous...

Yes, in a way, they are, and you are correct to state that the functionality of the original script is not altered by their absence. My intention in putting them there was only to emphasize how a child script (such as a background process, or a parenthesized section in a script) can pass data to it's parent.

I'm very glad you find the thread interesting, and I appreciate the multiple features of different shells you have provided.

EDIT:

Now that I take a good look at it, you're right! The background process...

echo 'value' > $TMPFIFO &

...is already in a subshell!, so yes, the parentheses come out as completely superfluous... I get you now, loud and clear. In that sense, I guess you could say that the parentheses show that, no matter how deep the ancestry gets, the fifo will always be there to communicate all of it's levels.

---------- Post updated at 02:37 PM ---------- Previous update was at 02:04 PM ----------

Ok, here; what is the difference between using exec and doing...

{  sleep 2; ps -f ;echo "hello from $FIFO" ;} > "$FIFO" &

...instead? I seem to get the same results with both. But you say that:

What do you mean by "goes on after the first command writes an EOF"?

EDIT:

Unless you're talking about using exec instead of using redirection for each write statement (as opposed to for the whole curly braces, which is what I did here). For example, when I tried:

#!/bin/bash
FIFO="/tmp/fifo.$$"
if ! [ -p "$FIFO" ]; then
  mkfifo "$FIFO"
fi
{ echo 1 > $FIFO ; echo 2 > $FIFO ; echo 3 > $FIFO ; echo 4 > $FIFO ; echo 5 > $FIFO ; } &
var=$(< "$FIFO")
rm "$FIFO"
echo "$var"

I got varying results for each time I ran it. Fifo redirection for each write statement malfunctions here.

EDIT2:

BTW, thanks for the tip on coprocesses, I've never really used them, or even heard about them before, but it's good to know that the option exists.

I mean that one should avoid:

{ sleep 2; ps -f > "$FIFO"; echo "hello from $FIFO" > "$FIFO" ;} &

using exec is just one way of avoiding that...

{ exec >"$FIFO"; sleep 2; ps -f ;echo "hello from $FIFO" ;} &

regarding EDIT: That's right

It does not malfunction, the subprocess opens and closes stdout and therefore the varying results. I used a sleep statement so that the reading process would be reading before the subprocess started opening the pipe...

Ahhh... so the trick is in the timing then... Nice one.

---------- Post updated 03-27-12 at 12:57 PM ---------- Previous update was 03-26-12 at 03:08 PM ----------

I recently found this unusual shell notation in a linux journal article:

command <(compound-list)

This creates a temporal fifo which is inputted whatever output the list of commands compound-list produces, and thus, command treats this output as if it belonged to a file. This way, <(compound-list) is treated as if it were a common filename. This must be an exclusive bash feature, since I don't recall reading of it in any of the shell programming books I've read, and when I tried looking it up in POSIX.1-2008 (not too thoroughly, I admit) I found it also says nothing about this feature. The feature appears in the Bash Reference Manual, under the section 3.5.6. Process Substitution.

Anyway, I thought it was worth mentioning it, since it's loosely related to this thread.