将 STDERR 和 STDOUT 重定向到不同的变量,无需临时文件

将 STDERR 和 STDOUT 重定向到不同的变量,无需临时文件
func() {
    echo 'hello'
    echo 'This is an error' >&2
}

a=$(func)
b=???

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

 echo $b
 # output should be: "This is an error"

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

touch temp.txt
exec 3< temp.txt
a=$(func 2> temp.txt);
cat <&3
rm temp.txt

所以问题是,我如何stderr重定向函数func到变量b而不需要临时文件?

答案1

在 Linux 上以及使用可写临时文件实现此处文档的 shell(如5.1zshbash5.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"

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

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

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

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

无论如何,您都希望使用临时文件。任何使用管道的解决方案在输出较大的情况下都容易出现死锁。您可以通过两个单独的管道读取 stdout 和 stderr ,并在循环中使用select()/poll()和一些读取来读取来自两个管道的数据,而不会导致锁定,但这会非常复杂,并且 AFAIK,只有zsh内置select()支持并且只有yash一个原始接口pipe()(更多信息请参见使用 shell 重定向读取/写入同一文件描述符)。

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

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

(假设该命令不输出任何 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

)

假设 GNUgrep且线路足够短。如果行大于 PIPEBUF(Linux 上为 4K),两个 s 的输出行grep最终可能会被分成块。

答案2

好吧,在没有临时文件的情况下捕获一个变量中的 stderr 和另一个变量中的 stdout 根本不是一件容易的事。

这是一个有效的例子

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

result=$(
    { stdout=$(func) ; } 2>&1
    echo -e "mysuperuniqueseparator\n"
    echo -e "${stdout}\n"
)
var_out=${result#*mysuperuniqueseparator$'\n'}
var_err=${result%$'\n'mysuperuniqueseparator*}

我对此不满意,因为这是一种肮脏的方式,将 stderr 重定向到 stdout 并将两者放入一个变量中,并在它们之间使用分隔符,然后将其分解为两部分。

加:

显然,这并不可靠,因为命令的标准输出或标准错误可能包含您使用的任何分隔符字符串。

取自这里http://mywiki.wooledge.org/BashFAQ/002

答案3

没有临时文件/fifos,也没有有趣的 eval/文件描述符等:

x=$((echo 'this is stdout'; echo 'this is stderr' 1>&2; exit 123) 2> >(sed -r 's/^/2/g') 1> >(sed -r 's/^/1/g'))

echo $? ### exit code is preserved
# 123

echo "$x" | sed '/^2/d;s/^1//g'  ### stdout
# this is stdout

echo "$x" | sed '/^1/d;s/^2//g'  ### stderr
# this is stderr

注意:对于大输出可能效率不高。

相关内容