如何在不创建子外壳的情况下将命令的输出存储在变量中 [Bash <v4]

Posted

技术标签:

【中文标题】如何在不创建子外壳的情况下将命令的输出存储在变量中 [Bash <v4]【英文标题】:How to store the output of command in a variable without creating a subshell [Bash <v4] 【发布时间】:2014-03-05 02:57:30 【问题描述】:

ksh 有一个非常有趣的结构来做到这一点,在这个答案中有详细说明:https://***.com/a/11172617/636849

从 Bash 4.0 开始,有一个内置的 ma​​pfile 内置命令应该可以解决这个问题: 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 中做到这一点?

【问题讨论】:

出于好奇,您将如何使用mapfile?据我所知,在任何版本中都没有 bash 等价物。 mapfile -t foo_output &lt;(foo) - 这是一个进程替换 - foo 在一个全新的进程中运行,我认为这不是你想要的。请参阅我的答案的附录。 我不熟悉mapfile,但由于它似乎读取其标准输入而不是文件,因此您可能必须编写&lt; &lt;(cmd) 而不仅仅是&lt;(cmd),如@987654331 @ 最终被替换为 /dev/fd/63 之类的东西(尝试 echo &lt;(echo) 进行检查)。至少它似乎没有那样挂。 【参考方案1】:

这是另一种方法,它的不同之处足以保证单独的答案。我认为这种方法是无 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:~$

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

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

【讨论】:

哇,虚拟 FD / FIFO 技巧太棒了!是的,它看起来像它的伎俩。您甚至可以定义一个“分配”函数:assign () local var=$1; shift; "$@" &gt; /tmp/myfifo; read $var &lt; /tmp/myfifo; 。我测试过,它可以工作:foo () local b; assign b bar; echo $b; @LucasCimon 请注意,它仅适用于单行输出,如果将/tmp 安装在内存上,它可能比子shell 更好,但如果不是这样,肯定会更糟创建磁盘访问。【参考方案2】:

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

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

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

【讨论】:

确实,这不是问题的答案,但很高兴知道。请注意,这适用于 bash 内置函数。 GNU coreutils printf(我有 8.3)不提供 -v 标志。 type -a printf 看看printfs 你有什么可用的。 builtin printf -v VARTOSET "put me in the var" 显式调用内置。【参考方案3】:

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

#!/bin/bash

foo ()  echo $BASH_SUBSHELL; 

mkfifo /tmp/fifo1,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/fifo1,2

这当然假设了两件事:

允许fifos 正在执行缓冲的命令组被允许进入后台

我测试了它可以在这些bash 版本中工作:

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 方法是否符合您的要求,因为进程替换 &lt;() 创建了一个全新的 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的顶层。

【讨论】:

非常有趣的答案,谢谢!我考虑过使用/dev/shm ,但命名管道是一个非常好的主意。不过有几个问题:1)buf 来自哪里? 2) 为什么在第一个循环中增加i?另外,您对mapfile 的解释非常完美! @LucasCimon - buf 只是我用来缓冲 fifo1 和 fifo2 之间的流的 bash 数组的名称。 从 shell 的角度来看,&lt;(cmd) 看起来像一个文件。所以你需要一个额外的&lt; 来重定向到mapfile。这就是mapfile 阻塞的原因——它实际上阻塞了标准输入。输入^D,它就会解锁。 哦,我不知道数组不需要声明。不过,您还需要一个持续运行的后台进程。根本没有进程/子外壳怎么办? 索引数组不需要显式声明。关联数组做declare -A array。完全没有fork 肯定会给我带来更大的挑战。【参考方案4】:

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

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

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

然后将输出转换回数组:

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

【讨论】:

非常好的答案。我不明白为什么这没有被接受。此解决方案是否仅适用于版本 4?谢谢你,@kenorb! 进程替换&lt; &lt;(cmd) 创建一个子shell...它是一个特殊的断开连接的子shell,但尽管如此..这可能是它不被“接受”的原因。

以上是关于如何在不创建子外壳的情况下将命令的输出存储在变量中 [Bash <v4]的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用存储库的情况下将Docker镜像从一个主机复制到另一个主机

如何在不在线公开的情况下将文件存储在 Wordpress 插件中?

如何在不创建嵌套数组的情况下将多个变量添加到数组

如何在不复制 C# 的情况下将结构数组元素提取到变量中?

如何从javascript(nodejs)程序调用python,而不创建子进程

如何在不知道宽度的情况下将元素存储在二维向量中?