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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 18:14:53  来源:igfitidea点击:

How to store standard error in a variable

bashshellredirectvariablesstderr

提问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 sedtoo.

请注意,需要分号(在经典 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.shscript through a command such as sedand save the stderrin a variable named error. The result of the pipe is sent to stdoutfor 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.

这个问题有很多重复,其中许多都有一个稍微简单的使用场景,您不想同时捕获 stderrstdout以及退出代码。

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 stderrof some commandinto varyou can do

如果你想赶上stderr一些commandvar你可以做

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

Afterwards you have it all:

之后你就拥有了一切:

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

If commandis 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 -naliases "$1" (which is the variable for catch-stderr)
  • 3>&1uses 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>&1redirects stderrto the output capturing $(..)
    • 1>&3redirects stdoutaway from the output capturing $(..)back to the "outer" stdoutwhich was saved in file descriptor 3. Note that stderrstill 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 commanddoes not suddenly has some unknown open file descriptor showing up. Note that the outer shell still has FD 3 open, but commandwill not see it.
    • The latter is important, because some programs like lvmcomplain about unexpected file descriptors. And lvmcomplains to stderr- just what we are going to capture!
  • local -n别名“$1”(这是 的变量catch-stderr
  • 3>&1使用文件描述符 3 来保存标准输出点
  • { command; }(或“$@”)然后在输出捕获中执行命令 $(..)
  • 请注意,这里的确切顺序很重要(以错误的方式执行会错误地打乱文件描述符):
    • 2>&1重定向stderr到输出捕获$(..)
    • 1>&3stdout从输出捕获重定向$(..)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>&1as argument 9followed 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-fdmust 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 as catch-stderr var cmd..
  • shift || returnis 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-bashas 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 evalis 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 bashand zsh, and the assignment to the variable in the subshell wouldn't be available to the current shell.

您不能在命令之外使用管道,因为管道在bashand 中创建了一个子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 teeto make a duplicate. In this case the output of teewill 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 teevia the special file /dev/fd/2since teeexpects a file path rather than a fd number.

如果要捕获和打印,请使用tee复制。在这种情况下,输出tee将被捕获$()而不是进入控制台,但 stderr(of tee) 仍将进入控制台,因此我们将其用作teevia 特殊文件的第二个输出,/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 teeat the end of the pipeline and the pipeline itself routes stdout of ./useless.shto the stdin of teeAFTER we swapped stdin and stdout for ./useless.sh.

注意:这是一行中的大量重定向,顺序很重要。 $()正在获取tee管道末端的 stdout ,并且管道本身将 stdout of 路由./useless.shtee我们将 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。