20

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 中做到这一点?


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

#!/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}

这当然假设了两件事:

  • 允许先进先出
  • 正在做缓冲的命令组被允许进入后台

我测试了它在这些版本中的工作:

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

附录

我不确定mapfilebash 4.x 中的方法是否符合您的要求,因为进程替换<()会创建一个全新的 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的顶层。

4

4 回答 4

18

这是另一种方法,它足够不同,需要单独的答案。我认为这种方法是无 subshel​​l 和无 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:~$ 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:~$

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

请注意,FIFO 将是一个有限大小的缓冲区,可能约为 4K,因此如果您的命令产生的输出比这多,它将再次阻塞。

于 2014-02-07T20:15:24.020 回答
5

在查看如何将任何“打印”命令的输出捕获到变量中时,这个问题经常出现。因此,对于任何寻找它的人来说都是可能的(从 bash v3.1.0 开始):

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

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

于 2018-03-16T13:08:17.663 回答
4

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

#!/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}

这当然假设了两件事:

  • 允许先进先出
  • 正在做缓冲的命令组被允许进入后台

我测试了它在这些版本中的工作:

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

附录

我不确定mapfilebash 4.x 中的方法是否符合您的要求,因为进程替换<()会创建一个全新的 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的顶层。

于 2014-02-07T18:20:01.353 回答
3

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

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

否则在函数中给出两项:

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

您可以使用readIFS根据需要修改)如下命令之一:

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 >4):

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

然后将输出转换回数组:

foo_arr=($foo_output)
subshell_depth=${foo_arr[0]} # should be 0
于 2016-02-27T15:03:28.183 回答