为了读者的方便,这里提供一个配方:
- 可作为一行命令重用,将stderr捕获到变量中
- 仍然可以访问命令的返回代码
- 牺牲了临时文件描述符3(当然可以由您更改)
- 并不会将该临时文件描述符暴露给内部命���
如果您想将某个命令的stderr
捕获到var
中,可以执行以下操作:
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
接下来你就拥有了所有:
echo "command gives $? and stderr '$var'";
如果
command
是简单的(不像
a | b
这样的语句),你可以省略内部的
{}
:
如果“command”很简单(不像“a | 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
将"$1"(即catch-stderr
变量)定义为别名(alias)
3>&1
使用文件描述符3保存标准输出(stdout)
{ command; }
(或"$@")然后在捕获输出的$(..)
中执行命令
- 请注意,确切的顺序在这里很重要(错误的方式会打乱文件描述符的排序):
2>&1
重定向stderr
到捕获输出的$(..)
1>&3
将stdout
重定向回“外部”stdout
,它是保存在文件描述符3中的。注意,stderr
仍然指向之前FD 1所指向的位置:$(..)
3>&-
然后关闭文件描述符3,因为它不再需要,以便于command
不会突然出现某些未知的打开文件描述符。请注意,外壳仍然保持FD 3处于打开状态,但command
将看不到它。
- 后者很重要,因为一些程序(如
lvm
)会抱怨出现了意外的文件描述符。而我们正要捕获stderr
。
如果您进行相应的调整,可以使用此示例捕获任何其他文件描述符。当然,除了文件描述符1之外(这里重定向逻辑会出错,但对于文件描述符1,您可以像平常一样使用var=$(command)
)。
请注意,这牺牲了文件描述符3。如果您需要该文件描述符,则可以更改编号。但是,请注意,一些(来自20世纪80年代的)shell可能将99>&1
解释为参数9
,然后再是9>&1
(对于bash
来说没有问题)。
还要注意,通过变量使FD 3可配置化并不特别容易。这使得事情变得非常难以阅读:
: 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
的前三个参数。始终以“静态”的方式明确给出它们。
因此,不要使用catch-var-from-fd-by-fd $var $fda $fdb $command
,永远不要这样做!
如果您必须传递一个变量变量名,请至少按以下方式执行:local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
这仍然不能保护您免受所有攻击,但至少有助于检测和避免常见的脚本错误。
注:
catch-var-from-fd-by-fd var 2 3 cmd..
与catch-stderr var cmd..
相同。
shift || return
只是一种防止忘记提供正确数量参数时出现丑陋错误的方法。也许终止shell是另一种方法(但这使得从命令行测试变得困难)。
- 该程序被编写得更易于理解。可以重写函数,使其不需要
exec
,但那样会变得非常丑陋。
- 此例程也可以为非
bash
重写,使其不需要local -n
。但是,那样您将无法使用本地变量,并且会变得极其丑陋!
- 还要注意,
eval
是以安全的方式使用的。通常,eval
被认为是危险的。但在这种情况下,它不比使用"$@"
(执行任意命令)更危险。但请务必使用如此精确和正确的引号(否则会变得非常非常危险)。
ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)
。 - Tim Kersten