bash 如何在变量中存储标准误差
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/962255/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to store standard error in a variable
提问by psycotica0
Let's say I have a script like the following:
假设我有一个如下所示的脚本:
useless.sh
没用的.sh
echo "This Is Error" 1>&2
echo "This Is Output"
And I have another shell script:
我还有另一个 shell 脚本:
alsoUseless.sh
也无用的.sh
./useless.sh | sed 's/Output/Useless/'
I want to capture "This Is Error", or any other stderr from useless.sh, into a variable. Let's call it ERROR.
我想将“这是错误”或 useless.sh 中的任何其他 stderr 捕获到一个变量中。我们称之为错误。
Notice that I am using stdout for something. I want to continue using stdout, so redirecting stderr into stdout is not helpful, in this case.
请注意,我正在使用 stdout 进行某些操作。我想继续使用 stdout,因此在这种情况下,将 stderr 重定向到 stdout 没有帮助。
So, basically, I want to do
所以,基本上,我想做
./useless.sh 2> $ERROR | ...
but that obviously doesn't work.
但这显然行不通。
I also know that I could do
我也知道我可以做到
./useless.sh 2> /tmp/Error
ERROR=`cat /tmp/Error`
but that's ugly and unnecessary.
但这是丑陋和不必要的。
Unfortunately, if no answers turn up here that's what I'm going to have to do.
不幸的是,如果这里没有答案,那就是我将要做的。
I'm hoping there's another way.
我希望有另一种方式。
Anyone have any better ideas?
有人有更好的想法吗?
采纳答案by Jonathan Leffler
It would be neater to capture the error file thus:
因此捕获错误文件会更整洁:
ERROR=$(</tmp/Error)
The shell recognizes this and doesn't have to run 'cat
' to get the data.
shell 识别出这一点,并且不必运行“ cat
”来获取数据。
The bigger question is hard. I don't think there's an easy way to do it. You'd have to build the entire pipeline into the sub-shell, eventually sending its final standard output to a file, so that you can redirect the errors to standard output.
更大的问题很难。我不认为有一种简单的方法可以做到这一点。您必须将整个管道构建到子 shell 中,最终将其最终标准输出发送到文件,以便您可以将错误重定向到标准输出。
ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )
Note that the semi-colon is needed (in classic shells - Bourne, Korn - for sure; probably in Bash too). The '{}
' does I/O redirection over the enclosed commands. As written, it would capture errors from sed
too.
请注意,需要分号(在经典 shell 中 - Bourne、Korn - 当然;可能在 Bash 中也是如此)。' {}
' 对所包含的命令进行 I/O 重定向。正如所写,它也会从中捕获错误sed
。
WARNING:Formally untested code - use at own risk.
警告:正式未经测试的代码 - 使用风险自负。
回答by Paused until further notice.
alsoUseless.sh
也无用的.sh
This will allow you to pipe the output of your useless.sh
script through a command such as sed
and save the stderr
in a variable named error
. The result of the pipe is sent to stdout
for display or to be piped into another command.
这将允许您useless.sh
通过诸如命令的管道传输脚本的输出sed
并将其保存stderr
在名为error
. 管道的结果被发送到stdout
显示或通过管道传输到另一个命令。
It sets up a couple of extra file descriptors to manage the redirections needed in order to do this.
它设置了几个额外的文件描述符来管理执行此操作所需的重定向。
#!/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
回答by Chas. Owens
Redirected stderr to stdout, stdout to /dev/null, and then use the backticks or $()
to capture the redirected stderr:
将 stderr 重定向到 stdout,将 stdout 重定向到 /dev/null,然后使用反引号或$()
捕获重定向的 stderr:
ERROR=$(./useless.sh 2>&1 >/dev/null)
回答by tripleee
There are a lot of duplicates for this question, many of which have a slightly simpler usage scenario where you don't want to capture stderr andstdout andthe exit code all at the same time.
这个问题有很多重复,其中许多都有一个稍微简单的使用场景,您不想同时捕获 stderr和stdout以及退出代码。
if result=$(useless.sh 2>&1); then
stdout=$result
else
rc=$?
stderr=$result
fi
works for the common scenario where you expect either proper output in the case of success, or a diagnostic message on stderr in the case of failure.
适用于您期望在成功的情况下正确输出或在失败的情况下 stderr 上的诊断消息的常见场景。
Note that the shell's control statements already examine $?
under the hood; so anything which looks like
请注意,shell 的控制语句已经在底层$?
进行了检查;所以任何看起来像
cmd
if [ $? -eq 0 ], then ...
is just a clumsy, unidiomatic way of saying
只是一种笨拙、单调的说法
if cmd; then ...
回答by human9
# command receives its input from stdin.
# command sends its output to stdout.
exec 3>&1
stderr="$(command </dev/stdin 2>&1 1>&3)"
exitcode="${?}"
echo "STDERR: $stderr"
exit ${exitcode}
回答by Tino
For the benefit of the reader, this recipe here
为了读者的利益,这个食谱在这里
- can be re-used as oneliner to catch stderr into a variable
- still gives access to the return code of the command
- Sacrifices a temporary file descriptor 3 (which can be changed by you of course)
- And does not expose this temporary file descriptors to the inner command
- 可以重新用作 oneliner 将 stderr 捕获到变量中
- 仍然可以访问命令的返回代码
- 牺牲一个临时文件描述符 3(当然你可以改变)
- 并且不会将此临时文件描述符暴露给内部命令
If you want to catch stderr
of some command
into var
you can do
如果你想赶上stderr
一些command
到var
你可以做
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Afterwards you have it all:
之后你就拥有了一切:
echo "command gives $? and stderr '$var'";
If command
is simple (not something like a | b
) you can leave the inner {}
away:
如果command
很简单(不是类似的东西a | b
),你可以离开内部{}
:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
Wrapped into an easy reusable bash
-function (probably needs version 3 and above for local -n
):
包装成一个简单的可重用bash
函数(可能需要版本 3 及更高版本local -n
):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v=""; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
Explained:
解释:
local -n
aliases "$1" (which is the variable forcatch-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
redirectsstderr
to the output capturing$(..)
1>&3
redirectsstdout
away from the output capturing$(..)
back to the "outer"stdout
which was saved in file descriptor 3. Note thatstderr
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 thatcommand
does not suddenly has some unknown open file descriptor showing up. Note that the outer shell still has FD 3 open, butcommand
will not see it.- The latter is important, because some programs like
lvm
complain about unexpected file descriptors. Andlvm
complains tostderr
- just what we are going to capture!
local -n
别名“$1”(这是 的变量catch-stderr
)3>&1
使用文件描述符 3 来保存标准输出点{ command; }
(或“$@”)然后在输出捕获中执行命令$(..)
- 请注意,这里的确切顺序很重要(以错误的方式执行会错误地打乱文件描述符):
2>&1
重定向stderr
到输出捕获$(..)
1>&3
stdout
从输出捕获重定向$(..)
回stdout
保存在文件描述符 3 中的“外部” 。请注意,stderr
仍然指之前 FD 1 指向的位置:到输出捕获$(..)
3>&-
然后关闭文件描述符 3,因为它不再需要,这样command
就不会突然出现一些未知的打开文件描述符。请注意,外壳仍然打开 FD 3,但command
不会看到它。- 后者很重要,因为一些程序喜欢
lvm
抱怨意外的文件描述符。并lvm
抱怨stderr
- 正是我们要捕捉的!
You can catch any other file descriptor with this recipe, if you adapt accordingly. Except file descriptor 1 of course (here the redirection logic would be wrong, but for file descriptor 1 you can just use var=$(command)
as usual).
如果您相应地进行调整,您可以使用此配方捕获任何其他文件描述符。当然,除了文件描述符 1(这里的重定向逻辑是错误的,但是对于文件描述符 1,您可以var=$(command)
照常使用)。
Note that this sacrifices file descriptor 3. If you happen to need that file descriptor, feel free to change the number. But be aware, that some shells (from the 1980s) might understand 99>&1
as argument 9
followed by 9>&1
(this is no problem for bash
).
请注意,这会牺牲文件描述符 3。如果您碰巧需要该文件描述符,请随时更改编号。但请注意,某些 shell(来自 1980 年代)可能会理解99>&1
为参数9
后跟9>&1
(这对 来说没有问题bash
)。
Also note that it is not particluar easy to make this FD 3 configurable through a variable. This makes things very unreadable:
另请注意,通过变量使此 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="";
local fd1="" fd2="";
shift 3 || return;
eval exec "$fd2>&1";
v="$(eval '"$@"' "$fd1>&1" "1>&$fd2" "$fd2>&-")";
eval exec "$fd2>&-";
}
Security note:The first 3 arguments to
catch-var-from-fd-by-fd
must not be taken from a 3rd party. Always give them explicitly in a "static" fashion.So no-no-no
catch-var-from-fd-by-fd $var $fda $fdb $command
, never do this!If you happen to pass in a variable variable name, at least do it as follows:
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
This still will not protect you against every exploit, but at least helps to detect and avoid common scripting errors.
安全说明:
catch-var-from-fd-by-fd
不得从第 3 方获取前 3 个参数。始终以“静态”方式明确地给出它们。所以不不不
catch-var-from-fd-by-fd $var $fda $fdb $command
,永远不要这样做!如果你碰巧传入了一个变量变量名,至少要这样做:
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
这仍然不能保护您免受每个漏洞的侵害,但至少有助于检测和避免常见的脚本错误。
Notes:
笔记:
catch-var-from-fd-by-fd var 2 3 cmd..
is the same ascatch-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 forlocal -n
. However then you cannot use local variables and it gets extremely ugly! - Also note that the
eval
s are used in a safe fashion. Usuallyeval
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).
catch-var-from-fd-by-fd var 2 3 cmd..
是相同的catch-stderr var cmd..
shift || return
如果您忘记提供正确数量的参数,这只是防止丑陋错误的一些方法。也许终止 shell 是另一种方式(但这使得很难从命令行进行测试)。- 程序是这样写的,这样更容易理解。可以重写该函数使其不需要
exec
,但随后它变得非常丑陋。 - 此例程也可以为非重写,
bash
这样就不需要local -n
. 但是,你不能使用局部变量,它变得非常难看! - 另请注意,
eval
以安全的方式使用 s。通常eval
被认为是危险的。然而,在这种情况下,它并不比使用"$@"
(执行任意命令)更邪恶。但是,请务必使用此处显示的准确和正确的引用(否则会变得非常危险)。
回答by tfga
Here's how I did it :
这是我如何做到的:
#
# - name of the (global) variable where the contents of stderr will be stored
# - command to be executed
#
captureStderr()
{
local tmpFile=$(mktemp)
2> $tmpFile
eval "=$(< $tmpFile)"
rm $tmpFile
}
Usage example :
用法示例:
captureStderr err "./useless.sh"
echo -$err-
It doesuse a temporary file. But at least the ugly stuff is wrapped in a function.
它确实使用了一个临时文件。但至少丑陋的东西被包裹在一个函数中。
回答by tfga
This is an interesting problem to which I hoped there was an elegant solution. Sadly, I end up with a solution similar to Mr. Leffler, but I'll add that you can call useless from inside a Bash function for improved readability:
这是一个有趣的问题,我希望有一个优雅的解决方案。可悲的是,我最终得到了一个类似于 Leffler 先生的解决方案,但我要补充的是,您可以从 Bash 函数内部调用 useless 以提高可读性:
#!/bin/bash function useless { /tmp/useless.sh | sed 's/Output/Useless/' } ERROR=$(useless) echo $ERROR
All other kind of output redirection must be backed by a temporary file.
所有其他类型的输出重定向都必须由临时文件支持。
回答by Tom Hale
POSIX
POSIX
STDERR can be captured with some redirection magic:
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
Note that piping of STDOUT of the command (here ls
) is done inside the innermost {
}
. If you're executing a simple command (eg, not a pipe), you could remove these inner braces.
请注意,命令(此处ls
)的 STDOUT 管道是在最里面的{
}
. 如果您正在执行一个简单的命令(例如,不是管道),您可以删除这些内部大括号。
You can't pipe outside the command as piping makes a subshell in bash
and zsh
, and the assignment to the variable in the subshell wouldn't be available to the current shell.
您不能在命令之外使用管道,因为管道在bash
and 中创建了一个子zsh
外壳,并且当前外壳无法对子外壳中的变量进行赋值。
bash
猛击
In bash
, it would be better not to assume that file descriptor 3 is unused:
在 中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
Note that this doesn't work in zsh
.
请注意,这在zsh
.
Thanks to this answerfor the general idea.
感谢这个答案的总体思路。
回答by SensorSmith
Capture AND Print stderr
捕获和打印标准错误
ERROR=$( ./useless.sh 3>&1 1>&2 2>&3 | tee /dev/fd/2 )
Breakdown
分解
You can use $()
to capture stdout, but you want to capture stderr instead. So you swap stdout and stderr. Using fd 3 as the temporary storage in the standard swap algorithm.
您可以使用$()
来捕获 stdout,但您想改为捕获 stderr。所以你交换标准输出和标准错误。使用 fd 3 作为标准交换算法中的临时存储。
If you want to capture AND print use tee
to make a duplicate. In this case the output of tee
will be captured by $()
rather than go to the console, but stderr(of tee
) will still go to the console so we use that as the second output for tee
via the special file /dev/fd/2
since tee
expects a file path rather than a fd number.
如果要捕获和打印,请使用tee
复制。在这种情况下,输出tee
将被捕获$()
而不是进入控制台,但 stderr(of tee
) 仍将进入控制台,因此我们将其用作tee
via 特殊文件的第二个输出,/dev/fd/2
因为tee
需要文件路径而不是 fd数字。
NOTE: That is an awful lot of redirections in a single line and the order matters. $()
is grabbing the stdout of tee
at the end of the pipeline and the pipeline itself routes stdout of ./useless.sh
to the stdin of tee
AFTER we swapped stdin and stdout for ./useless.sh
.
注意:这是一行中的大量重定向,顺序很重要。 $()
正在获取tee
管道末端的 stdout ,并且管道本身将 stdout of 路由./useless.sh
到tee
我们将 stdin 和 stdout 交换为之后的 stdin ./useless.sh
。
Using stdout of ./useless.sh
使用 ./useless.sh 的标准输出
The OP said he still wanted to use (not just print) stdout, like ./useless.sh | sed 's/Output/Useless/'
.
OP 说他仍然想使用(不仅仅是打印)标准输出,比如./useless.sh | sed 's/Output/Useless/'
.
No problem just do it BEFORE swapping stdout and stderr. I recommend moving it into a function or file (also-useless.sh) and calling that in place of ./useless.sh in the line above.
没问题,只需在交换 stdout 和 stderr 之前执行即可。我建议将它移动到一个函数或文件(也是-useless.sh)中,并在上面的行中调用它来代替 ./useless.sh。
However, if you want to CAPTURE stdout AND stderr, then I think you have to fall back on temporary files because $()
will only do one at a time and it makes a subshell from which you cannot return variables.
但是,如果您想捕获 stdout 和 stderr,那么我认为您必须依靠临时文件,因为一次$()
只能执行一个,并且它会生成一个无法从中返回变量的子shell。