为了读者的利益,此食谱在这里
- 可以重新用作oneliner以将stderr捕获到变量中
- 仍然可以访问命令的返回码
- 牺牲一个临时文件描述符3(当然,您可以更改它)
- 并且不会向内部命令公开此临时文件描述符
如果你想赶上stderr
一些command
到var
你可以做
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
之后,您拥有了所有这些:
echo "command gives $? and stderr '$var'";
如果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
)
3>&1
使用文件描述符3保存那里的stdout点
{ command; }
(或“ $ @”),然后在输出捕获中执行命令 $(..)
- 请注意,确切的顺序在这里很重要(用错误的方式错误地乱码文件描述符):
2>&1
重定向stderr
到输出捕获$(..)
1>&3
stdout
从输出捕获重定向$(..)
到stdout
保存在文件描述符3中的“外部” 。请注意,stderr
仍指FD 1指向的位置:到输出捕获$(..)
3>&-
然后由于不再需要而关闭文件描述符3,因此command
不会突然出现一些未知的打开文件描述符。请注意,外壳仍然打开了FD 3,但command
看不到它。
- 后者很重要,因为有些程序会
lvm
抱怨意外的文件描述符。并lvm
抱怨stderr
-正是我们要捕获的东西!
如果进行了相应的调整,则可以使用此配方捕获任何其他文件描述符。当然,除了文件描述符1(这里的重定向逻辑是错误的,但是对于文件描述符1,您可以var=$(command)
照常使用)。
请注意,这会牺牲文件描述符3。如果您碰巧需要该文件描述符,请随时更改数字。但请注意,某些shell(从1980年代开始)可能将其理解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>&-";
}
安全说明:的前3个参数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
这只是防止丑陋错误的一种方法,以防万一您忘记提供正确数量的参数。也许终止外壳是另一种方式(但这使从命令行进行测试变得困难)。
- 该例程的编写方式使其更易于理解。可以重写该函数,使其不需要
exec
,但随后变得非常难看。
- 也可以将该例程改写为非例程,从而
bash
不需要local -n
。但是,那么您将无法使用局部变量,并且它变得非常难看!
- 另请注意,
eval
以安全的方式使用。通常eval
认为是危险的。但是,在这种情况下,它没有比使用"$@"
(执行任意命令)更邪恶。但是,请务必使用此处显示的正确和正确的引用(否则它会非常非常危险)。
ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)