为了方便读者,这是食谱
可以重新使用作为联机捕获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).