假设我有一个像下面这样的脚本:
useless.sh
echo "This Is Error" 1>&2
echo "This Is Output"
我有另一个shell脚本:
alsoUseless.sh
./useless.sh | sed 's/Output/Useless/'
我想捕捉“这是错误”,或任何其他stderr从无用的。sh,到一个变量。
我们称它为ERROR。
注意,我使用了stdout。我想继续使用stdout,所以在这种情况下,将stderr重定向到stdout没有帮助。
所以,基本上,我想做
./useless.sh 2> $ERROR | ...
但这显然行不通。
我也知道我能做到
./useless.sh 2> /tmp/Error
ERROR=`cat /tmp/Error`
但这是丑陋和不必要的。
不幸的是,如果这里没有答案,这就是我要做的。
我希望还有别的办法。
有人有更好的主意吗?
这个问题有很多重复的地方,其中许多都有一个稍微简单的使用场景,您不希望同时捕获stderr、stdout和退出代码。
if result=$(useless.sh 2>&1); then
stdout=$result
else
rc=$?
stderr=$result
fi
适用于常见的场景,在这种场景中,您希望在成功的情况下得到正确的输出,或者在失败的情况下在stderr上得到诊断消息。
注意,shell的控制语句已经检查了$?在引擎盖下;所以任何看起来像
cmd
if [ $? -eq 0 ], then ...
只是一种笨拙的,不常用的说法吗
if cmd; then ...
如果您想绕过临时文件的使用,可以使用进程替换。我还没把它弄好。这是我的第一次尝试:
$ .useless.sh 2> >( ERROR=$(<) )
-bash: command substitution: line 42: syntax error near unexpected token `)'
-bash: command substitution: line 42: `<)'
然后我尝试了
$ ./useless.sh 2> >( ERROR=$( cat <() ) )
This Is Output
$ echo $ERROR # $ERROR is empty
然而
$ ./useless.sh 2> >( cat <() > asdf.txt )
This Is Output
$ cat asdf.txt
This Is Error
所以过程替代通常是正确的…不幸的是,每当我在>()内用$()中的东西包装STDIN以试图将其捕获到一个变量时,我就会丢失$()的内容。我认为这是因为$()启动了一个子进程,该子进程不再访问父进程所拥有的/dev/fd中的文件描述符。
进程替换为我购买了与不再在STDERR中的数据流工作的能力,不幸的是,我似乎无法以我想要的方式操纵它。
POSIX
STDERR可以通过一些重定向魔法来捕获:
$ { error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&3 ; } 2>&1); } 3>&1
lrwxrwxrwx 1 rZZt rZZt 7 Aug 22 15:44 /bin -> usr/bin/
$ echo $error
ls: cannot access '/XXXX': No such file or directory
注意,命令的STDOUT管道(这里是ls)是在最里面的{}中完成的。如果您正在执行一个简单的命令(例如,不是管道),您可以删除这些内部大括号。
您不能在命令外部进行管道操作,因为管道在bash和zsh中生成了子shell,并且当前shell无法使用对子shell中变量的赋值。
bash
在bash中,最好不要假设文件描述符3未使用:
{ error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&$tmp ; } 2>&1); } {tmp}>&1;
exec {tmp}>&- # With this syntax the FD stays open
注意,这在zsh中不起作用。
感谢这个大致的答案。
捕获并打印标准码
ERROR=$( ./useless.sh 3>&1 1>&2 2>&3 | tee /dev/fd/2 )
分解
您可以使用$()来捕获stdout,但您希望捕获stderr。你交换了stdout和stderr。在标准交换算法中使用fd 3作为临时存储。
如果你想捕捉和打印使用tee做一个副本。在这种情况下,tee的输出将被$()捕获,而不是转到控制台,但stderr(的tee)仍然会转到控制台,因此我们使用它作为tee的第二个输出,通过特殊文件/dev/fd/2,因为tee需要一个文件路径而不是fd数。
注意:这是一个可怕的多重定向在单行和顺序问题。$()在管道的末尾抓取tee的stdout,并且管道本身将./useless.sh的stdout路由到tee的stdin,在我们将stdin和stdout交换为./useless.sh之后。
使用./useless.sh的stdout
OP说他仍然想使用(不仅仅是打印)标准输出,比如。/Useless .sh | sed 's/Output/Useless/'。
没问题,只是在交换stdout和stderr之前这样做。我建议将它移动到一个函数或文件中(同样是useless.sh),并调用它来代替上面一行中的./useless.sh。
然而,如果你想要捕获标准输出和标准输出,那么我认为你必须回到临时文件,因为$()一次只会做一个,并且它会生成一个子shell,从中你不能返回变量。