捕获不带子 shell 的 shell 函数的输出

捕获不带子 shell 的 shell 函数的输出

rbenv在机器上安装了(ruby 版本管理器),它的工作原理如下:

$ rbenv local
2.3.1

写信给标准输出我的 ruby​​ 的本地版本。我想挽救这个版本并将其声明在变量中以便在其他场合重用。

$ declare -r RUBY_DEFINED_VERSION=$(rbenv local)
$ echo Using ruby version $RUBY_DEFINED_VERSION
Using ruby version 2.3.1

有用!

但我不想使用子外壳完成工作(使用$()``)。我想用同样的我不想创建一个tmp文件来完成工作。

有没有办法做到这一点?

笔记: declare -r不是强制性的,可以是简单的var=FOOBAR

答案1

有一个 hack,但我认为如果你需要循环使用它,它就有意义。

你可以cat coproc像这样打开:coproc CAT { cat; }

这将cat在后台启动一个命令,并设置两个环境变量:CAT_PIDCAT。该CAT变量是一个数组,其中包含STDOUTSTDIN(按此顺序)使用的文件描述符(管道)cat

因此,您可以执行将输出写入&${CAT[1]}代表 的任何内容STDIN,并使用内置命令read设置从中读取的变量${CAT[0]}STDOUTcat 的变量。

这是一个示例:

coproc CAT { cat; }
echo 123 >&${CAT[1]}
read myvar <&${CAT[0]}

去测试:

echo $myvar
123

使用后不要忘记阻止猫。您可以通过终止进程来做到这一点。

kill $CAT_PID

这对性能调优产生了很大的影响。

更新:bash实现null分隔字符串。所以在处理二进制数据时,read确实很棘手。您可以一次读取LC_ALL=C read -r -n1 -d $'\0'一个字节,然后 null 将是${REPLY}变量上的空字符串。

答案2

使用 bash 你也可以这样做:

read a < <(echo hello)
echo "$a"

或者像这样:

shopt -s lastpipe
echo hello | read a
shopt -u lastpipe
echo "$a"

但你仍然必须启动一个将运行 ruby​​ 的子进程,所以我真的不明白你想避免什么......

答案3

如果在 Linux 上,使用bash5.1(或 zsh)之前的版本,您可以执行以下操作:

{
  chmod u+w /dev/fd/3 # only needed in bash 5.0
  rbenv local > /dev/fd/3
  IFS= read -rd '' -u 3 variable
} 3<<< ''

这确实使用了像每个此处文档或此处字符串一样的临时文件,尽管它对您来说是隐藏的。

bash5.1 改用管道而不是常规临时文件(至少当herefile/herestring 的内容足够小以适合管道缓冲区时)。有了这些,您始终可以手动创建临时文件:

tmpfile=$(mktemp) || exit
{
  rm -f -- "$tmpfile"
  rbenv local >&3
  IFS= read -rd '' -u4 variable
} 3> "$tmpfile" 4< "$tmpfile"

<<(像/ do一样尽早删除 tmpfile,以最大限度地减少脚本被杀死或退出 shell<<<时留下的风险)。rbenv

如果rbenv输出的数据少于管道中可以容纳的数据(通常为 64KiB),则仍然在 Linux 和仅 Linux 上,您可以使用管道代替临时文件:

{
  rbenv local > /dev/fd/3
  IFS= read -rd '' -u 3 variable
} 3< <(:)

对于ksh93或最新版本mksh,请使用不启动子 shell 的命令替换形式:

variable=${
  rbenv local
}

目前,bash 和 zsh 的开发版本中也提供了该版本。

请注意,与该方法相反IFS= read -rd '',它会删除输出中的尾随换行符(就像常规命令替换一样)。

为了实现这一点,当前(截至 2024 年)版本的 ksh93 在内部使用预先删除的临时文件(在/dev/shm我的 Debian 系统上)、mksh(在/tmp我的系统上)、zsh-dev 相同,只是文件稍后被删除,并且bash-dev 匿名记忆力支持的地方有临时文件,不支持的地方有预先删除的临时文件/tmp

在旧版本中zsh,您利用=(...)进程替换的形式来获取自动删除的临时文件(此处作为参数传递给匿名函数):

() { rbenv local > $1; IFS= read -rd '' variable <$1; } =()

1 但就 zsh 而言,${ cmd }目前正在争论是否这样做或仅在不加引号时这样做。

相关内容