在没有临时文件的情况下将 STDERR 和 STDOUT 重定向到不同的变量

sma*_*ber 9 bash io-redirection shell-script stderr variable

func() {
    echo 'hello'
    echo 'This is an error' >&2
}

a=$(func)
b=???
Run Code Online (Sandbox Code Playgroud)

我想将 stderr 重定向到b变量而不创建临时文件。

 echo $b
 # output should be: "This is an error"
Run Code Online (Sandbox Code Playgroud)

有效但使用临时文件的解决方案:

touch temp.txt
exec 3< temp.txt
a=$(func 2> temp.txt);
cat <&3
rm temp.txt
Run Code Online (Sandbox Code Playgroud)

所以问题是,如何在不需要临时文件的情况stderr下将函数的重定向func到变量b

Sté*_*las 10

在Linux和与(如实现这里的文档与写的临时文件壳zshbash前5.1版本做),你可以这样做:

{
  out=$(
    chmod u+w /dev/fd/3 && # needed for bash5.0
      ls /dev/null /x 2> /dev/fd/3
  )
  status=$?
  err=$(cat<&3)
} 3<<EOF
EOF

printf '%s=<%s>\n' out "$out" err "$err" status "$status"
Run Code Online (Sandbox Code Playgroud)

(其中ls /dev/null /x是在 stdout 和 stderr 上输出某些内容的示例命令)。

使用zsh,您还可以执行以下操作:

(){ out=$(ls /dev/null /x 2> $1) status=$? err=$(<$1);} =(:)
Run Code Online (Sandbox Code Playgroud)

(其中=(cmd)是一种使用临时文件和(){ code; } args匿名函数的进程替换形式)。

在任何情况下,您都希望使用临时文件。在大输出的情况下,任何使用管道的解决方案都容易出现死锁。您可以通过两个独立的管道和使用读输出和错误select()/poll()和在一个循环中,因为它来自于两个管道,而不会造成拘留所读取数据的一些读,但会被相当复杂和AFAIK,只有zsh已经select()支持内置并且只有yash一个原始接口pipe()(更多关于使用外壳重定向读取/写入相同的文件描述符)。

另一种方法是将其中一个流存储在临时内存中而不是临时文件中。像(zshbash语法):

{
  IFS= read -rd '' err
  IFS= read -rd '' out
  IFS= read -rd '' status
} < <({ out=$(ls /dev/null /x); } 2>&1; printf '\0%s' "$out" "$?")
Run Code Online (Sandbox Code Playgroud)

(假设该命令不输出任何 NUL)

请注意,这$err将包括尾随的换行符。

其他方法可能是不同地装饰 stdout 和 stderr 并在阅读时删除装饰:

out= err= status=
while IFS= read -r line; do
  case $line in
    (out:*)    out=$out${line#out:}$'\n';;
    (err:*)    err=$err${line#err:}$'\n';;
    (status:*) status=${line#status:};;
  esac
done < <(
  {
    {
      ls /dev/null /x |
        grep --label=out --line-buffered -H '^' >&3
      echo >&3 "status:${PIPESTATUS[0]}" # $pipestatus[1] in zsh
    } 2>&1 |
      grep --label=err --line-buffered -H '^'
  } 3>&1

)
Run Code Online (Sandbox Code Playgroud)

这假设 GNUgrep并且行足够短。如果行大于 PIPEBUF(Linux 上为 4K),则两个greps的输出行最终可能会以块的形式混杂在一起。