How to store the output of command in a variable without creating a subshell [Bash <v4] How to store the output of command in a variable without creating a subshell [Bash <v4] bash bash

How to store the output of command in a variable without creating a subshell [Bash <v4]


Here's another way to do it, which is different enough that it warrants a separate answer. I think this method is subshell-free and bash sub-process free:

ubuntu@ubuntu:~$ bar () { echo "$BASH_SUBSHELL $BASHPID"; }ubuntu@ubuntu:~$ bar0 8215ubuntu@ubuntu:~$ mkfifo /tmp/myfifoubuntu@ubuntu:~$ exec 3<> /tmp/myfifoubuntu@ubuntu:~$ unlink /tmp/myfifoubuntu@ubuntu:~$ bar 1>&3ubuntu@ubuntu:~$ read -u3 aubuntu@ubuntu:~$ echo $a0 8215ubuntu@ubuntu:~$ exec 3>&-ubuntu@ubuntu:~$

The trick here is to use exec to open the FIFO in read-write mode with an FD, which seems to have the side-effect of making the FIFO non-blocking. Then you can redirect your command to the FD without it blocking, then read the FD.

Note that the FIFO will be a limited-size buffer, probably around 4K, so if your command produces more output than this, it will end up blocking again.


Here's what I could come up with - its a bit messy, but foo is run in the top-level shell context and its output is provided in the variable a in the top-level shell context:

#!/bin/bashfoo () { echo ${BASH_SUBSHELL}; }mkfifo /tmp/fifo{1,2}{    # block, then read everything in fifo1 into the buffer array    i=0    while IFS='' read -r ln; do        buf[$((i++))]="$ln"    done < /tmp/fifo1    # then write everything in the buffer array to fifo2    for i in ${!buf[@]}; do        printf "%s\n" "${buf[$i]}"    done > /tmp/fifo2} &foo > /tmp/fifo1read a < /tmp/fifo2echo $arm /tmp/fifo{1,2}

This of course assumes two things:

  • fifos are allowed
  • The command group that is doing the buffering is allowed to be put into the background

I tested this to work in these versions:

  • 3.00.15(1)-release (x86_64-redhat-linux-gnu)
  • 3.2.48(1)-release (x86_64-apple-darwin12)
  • 4.2.25(1)-release (x86_64-pc-linux-gnu)

Addendum

I'm not sure the mapfile approach in bash 4.x does what you want, as the process substitution <() creates a whole new bash process (though not a bash subshell within that bash process):

$ bar () { echo "$BASH_SUBSHELL $BASHPID"; }$ bar0 2636$ mapfile -t bar_output < <(bar)$ echo ${bar_output[0]}0 60780$ 

So while $BASH_SUBSHELL is 0 here, it is because it is at the top level of the new shell process 60780 in the process substitution.


This question comes up very often while looking how to just capture output of any "printing" command into variable. So for anyone looking it's possible (since bash v3.1.0) with:

printf -v VARIABLE_NAME "whatever you need here: %s" $ID

If you tweak your scripts for speed then you can use pattern of setting some global variable at the end of functions instead of just "echoing" it - use this with care, it's sometimes criticized as leading to hard to maintain code.