假设我有一个像下面这样的脚本:

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`

但这是丑陋和不必要的。

不幸的是,如果这里没有答案,这就是我要做的。

我希望还有别的办法。

有人有更好的主意吗?


当前回答

alsoUseless.sh

这将允许您通过sed等命令来输出无用的.sh脚本的输出,并将stderr保存在名为error的变量中。管道的结果被发送到stdout以供显示或传输到另一个命令中。

它设置了两个额外的文件描述符来管理执行此操作所需的重定向。

#!/bin/bash

exec 3>&1 4>&2 #set up extra file descriptors

error=$( { ./useless.sh | sed 's/Output/Useless/' 2>&4 1>&3; } 2>&1 )

echo "The message is \"${error}.\""

exec 3>&- 4>&- # release the extra file descriptors

其他回答

alsoUseless.sh

这将允许您通过sed等命令来输出无用的.sh脚本的输出,并将stderr保存在名为error的变量中。管道的结果被发送到stdout以供显示或传输到另一个命令中。

它设置了两个额外的文件描述符来管理执行此操作所需的重定向。

#!/bin/bash

exec 3>&1 4>&2 #set up extra file descriptors

error=$( { ./useless.sh | sed 's/Output/Useless/' 2>&4 1>&3; } 2>&1 )

echo "The message is \"${error}.\""

exec 3>&- 4>&- # release the extra file descriptors

重定向stderr到stdout, stdout到/dev/null,然后使用反勾号或$()捕获重定向的stderr:

ERROR=$(./useless.sh 2>&1 >/dev/null)

为了方便读者,这是食谱

可以重新使用作为联机捕获stderr到一个变量 仍然允许访问命令的返回代码 牺牲一个临时文件描述符3(当然可以由您更改) 并且不将此临时文件描述符公开给内部命令

如果你想将某些命令的stderr捕获到var中,你可以这样做

{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;

之后你就拥有了一切:

echo "command gives $? and stderr '$var'";

如果命令很简单(不是像| b那样),你可以去掉内部的{}:

{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;

包装成一个简单的可重用bash函数(可能需要版本3及以上的local -n):

: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }

解释道:

local -n aliases "$1" (which is the variable for catch-stderr) 3>&1 uses file descriptor 3 to save there stdout points { command; } (or "$@") then executes the command within the output capturing $(..) Please note that the exact order is important here (doing it the wrong way shuffles the file descriptors wrongly): 2>&1 redirects stderr to the output capturing $(..) 1>&3 redirects stdout away from the output capturing $(..) back to the "outer" stdout which was saved in file descriptor 3. Note that stderr still refers to where FD 1 pointed before: To the output capturing $(..) 3>&- then closes the file descriptor 3 as it is no more needed, such that command does not suddenly has some unknown open file descriptor showing up. Note that the outer shell still has FD 3 open, but command will not see it. The latter is important, because some programs like lvm complain about unexpected file descriptors. And lvm complains to stderr - just what we are going to capture!

如果进行相应的调整,则可以使用此配方捕获任何其他文件描述符。当然除了文件描述符1(这里的重定向逻辑是错误的,但是对于文件描述符1,您可以像往常一样使用var=$(命令))。

注意,这牺牲了文件描述符3。如果您恰好需要该文件描述符,可以随意更改数字。但是请注意,一些shell(从20世纪80年代开始)可能将99>&1理解为参数9,后面跟着9>&1(这对bash来说没有问题)。

还要注意的是,通过一个变量来配置这个fd3并不是特别容易。这使得内容非常难以阅读:

: catch-var-from-fd-by-fd variable fd-to-catch fd-to-sacrifice command [args..]
catch-var-from-fd-by-fd()
{
local -n v="$1";
local fd1="$2" fd2="$3";
shift 3 || return;

eval exec "$fd2>&1";
v="$(eval '"$@"' "$fd1>&1" "1>&$fd2" "$fd2>&-")";
eval exec "$fd2>&-";
}

安全注意:catch-var-from-fd-by-fd的前3个参数不能来自第三方。始终以“静态”方式明确地给出它们。 所以不-不-不catch-var-from-fd-by-fd $var $fda $fdb $命令,永远不要这样做! 如果你碰巧传递了一个变量名,至少要这样做: Local -n var="$var";Catch-var-from-fd-by-fd var 3 5 $命令 这仍然不能保护您免受所有攻击,但至少有助于检测和避免常见的脚本错误。

注:

catch-var-from-fd-by-fd var 2 3 cmd.. is the same as catch-stderr var cmd.. shift || return is just some way to prevent ugly errors in case you forget to give the correct number of arguments. Perhaps terminating the shell would be another way (but this makes it hard to test from commandline). The routine was written such, that it is more easy to understand. One can rewrite the function such that it does not need exec, but then it gets really ugly. This routine can be rewritten for non-bash as well such that there is no need for local -n. However then you cannot use local variables and it gets extremely ugly! Also note that the evals are used in a safe fashion. Usually eval is considerered dangerous. However in this case it is no more evil than using "$@" (to execute arbitrary commands). However please be sure to use the exact and correct quoting as shown here (else it becomes very very dangerous).

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中不起作用。


感谢这个大致的答案。

这篇文章帮助我想出了一个类似的解决方案:

MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`

然后只要MESSAGE不是空字符串,我们就把它传递给其他东西。这将让我们知道我们的format_logs.py是否因某种python异常而失败。