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.1zsh
或bash
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"
(其中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 重定向读取/写入同一文件描述符)。
另一种方法是将其中一个流存储在临时内存中而不是临时文件中。像(zsh
或bash
语法):
{
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 并将两者放入一个变量中,并在它们之间使用分隔符,然后将其分解为两部分。
加:
显然,这并不可靠,因为命令的标准输出或标准错误可能包含您使用的任何分隔符字符串。
答案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
注意:对于大输出可能效率不高。