Search code examples
bashvariable-assignmentsubshell

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


ksh has a really interesting construct to do this, detailed in this answer: https://stackoverflow.com/a/11172617/636849

Since Bash 4.0, there is a builtin mapfile builtin command that should solve this problem: http://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html

But strangely, it doesn't seem to work with process substitution:

foo () { echo ${BASH_SUBSHELL}; }
mapfile -t foo_output <(foo) # FAIL: hang forever here
subshell_depth=${foo_output[0]} # should be 0

But how to do this in Bash v3.2 ?


Solution

  • 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:~$ bar
    0 8215
    ubuntu@ubuntu:~$ mkfifo /tmp/myfifo
    ubuntu@ubuntu:~$ exec 3<> /tmp/myfifo
    ubuntu@ubuntu:~$ unlink /tmp/myfifo
    ubuntu@ubuntu:~$ bar 1>&3
    ubuntu@ubuntu:~$ read -u3 a
    ubuntu@ubuntu:~$ echo $a
    0 8215
    ubuntu@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.