如何在不创建子shell的情况下将命令输出存储在变量中[Bas​​h

时间:2014-02-07 15:51:26

标签: bash variable-assignment subshell

ksh 有一个非常有趣的构造来做到这一点,详见答案:https://stackoverflow.com/a/11172617/636849

从Bash 4.0开始,有一个内置的 mapfile 内置命令可以解决这个问题: http://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html

但奇怪的是,它似乎不适用于流程替换:

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

但是如何在Bash v3.2中执行此操作?

4 个答案:

答案 0 :(得分:14)

这是另一种方法,这是另一种方式,它可以保证单独的答案。我认为这个方法是无子shell和bash子进程免费的:

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

这里的技巧是使用exec以FD读写模式打开FIFO,这似乎具有使FIFO无阻塞的副作用。然后,您可以将命令重定向到FD而不阻塞它,然后读取FD。

请注意,FIFO将是一个有限大小的缓冲区,可能大约为4K,所以如果你的命令产生的输出多于此值,它将最终再次阻塞。

答案 1 :(得分:4)

这是我能想到的 - 它有点乱,但foo在顶层shell上下文中运行,其输出在顶层shell的变量a中提供上下文:

#!/bin/bash

foo () { 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/fifo1
read a < /tmp/fifo2
echo $a

rm /tmp/fifo{1,2}

这当然假设有两件事:

  • 允许使用fifos
  • 允许将执行缓冲的命令组放入后台

我测试了这个版本以适用于个版本:

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

附录

我不确定bash 4.x中的mapfile方法是否符合您的要求,因为进程替换<()创建了一个全新的bash进程(尽管不是bash进程中的bash子shell) ):

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

因此虽然$BASH_SUBSHELL在这里为0,但这是因为它在流程替换中位于新shell进程60780的顶层。

答案 2 :(得分:1)

最简单的方法是删除函数并直接传递变量,例如:

declare -a foo_output
mapfile -t foo_output <<<${BASH_SUBSHELL}
subshell_depth=${foo_output[0]} # Should be zero.

否则在函数中给出两个项目:

foo () { echo "$BASH_SUBSHELL $BASHPID"; }

您可以使用read(根据需要修改IFS),例如以下命令之一:

cat < <(foo) | read subshell_depth pid # Two variables.
read -r subshell_depth pid < <(foo) # Two separate variables.
read -a -r foo_arr < <(foo) # One array.

或使用readarray / mapfile(Bash&gt; 4):

mapfile -t foo_output < <(foo)
readarray -t foo_output < <(foo)

然后将输出转换回数组:

foo_arr=($foo_output)
subshell_depth=${foo_arr[0]} # should be 0

答案 3 :(得分:1)

在查看如何将任何“打印”命令的输出捕获到变量中时,这个问题经常出现。所以对于任何看起来都是可能的(自bash v3.1.0起):

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

如果你调整脚本的速度,那么你可以使用在函数末尾设置一些全局变量的模式而不是“回显”它 - 小心使用它,它有时被批评为导致难以维护的代码。 / p>