如何在变量中存储标准错误

问题描述 投票:155回答:15

假设我有一个如下脚本:

useless.是

echo "This Is Error" 1>&2
echo "This Is Output" 

我有另一个shell脚本:

also useless.是

./useless.sh | sed 's/Output/Useless/'

我想将“This Is Error”或者useless.sh中的任何其他stderr捕获到变量中。我们称之为ERROR。

请注意,我正在使用stdout。我想继续使用stdout,因此在这种情况下将stderr重定向到stdout是没有用的。

所以,基本上,我想做

./useless.sh 2> $ERROR | ...

但这显然不起作用。

我也知道我能做到

./useless.sh 2> /tmp/Error
ERROR=`cat /tmp/Error`

但那是丑陋和不必要的。

不幸的是,如果没有答案出现在这里,那就是我将要做的事情。

我希望还有另一种方式。

有没有更好的想法?

bash shell redirect variables stderr
15个回答
75
投票

因此捕获错误文件会更简洁:

ERROR=$(</tmp/Error)

shell识别出这一点,并且不必运行'cat'来获取数据。

更大的问题很难。我认为没有一种简单的方法可以做到这一点。您必须将整个管道构建到子shell中,最终将其最终标准输出发送到文件,以便您可以将错误重定向到标准输出。

ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )

请注意,需要使用分号(在经典的shell中 - Bourne,Korn - 肯定;也可能在Bash中)。 '{}'对所附命令执行I / O重定向。如上所述,它也会捕获来自sed的错误。

警告:正式未经测试的代码 - 使用风险自负。


1
投票

这篇文章帮助我为自己的目的提出了类似的解决方案:

MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`

然后只要我们的MESSAGE不是空字符串,我们就把它传递给其他东西。如果我们的format_logs.py因某种python异常而失败,这将告诉我们。


1
投票

捕获并打印stderr

ERROR=$( ./useless.sh 3>&1 1>&2 2>&3 | tee /dev/fd/2 )

分解

您可以使用$()捕获标准输出,但您想要捕获stderr。所以你交换了stdout和stderr。使用fd 3作为标准交换算法中的临时存储。

如果你想捕获和打印使用tee来复制。在这种情况下,tee的输出将被$()捕获而不是去控制台,但是stderr(tee)仍将进入控制台,因此我们通过特殊文件tee将其用作/dev/fd/2的第二个输出,因为tee预计文件路径而不是fd编号。

注意:在一行中有很多重定向,顺序很重要。 $()在管道末端抓住tee的标准,管道本身将./useless.sh的标准输送到tee的stdin,之后我们交换stdin和stdout为./useless.sh

使用./useless.sh的stdout

OP表示他仍然想使用(不仅仅是打印)stdout,比如./useless.sh | sed 's/Output/Useless/'

没有问题,只需要交换stdout和stderr。我建议将它移动到一个函数或文件(也是--useless.sh)中并在上面的行中调用它来代替./useless.sh。

但是,如果你想捕获stdout和stderr,那么我认为你必须回退临时文件,因为$()一次只能做一个并且它创建一个子shell,你不能从中返回变量。


0
投票

如果您想绕过临时文件的使用,您可以使用进程替换。我还没有完全开始工作。这是我的第一次尝试:

$ .useless.sh 2> >( ERROR=$(<) )
-bash: command substitution: line 42: syntax error near unexpected token `)'
-bash: command substitution: line 42: `<)'

然后我试了一下

$ ./useless.sh 2> >( ERROR=$( cat <() )  )
This Is Output
$ echo $ERROR   # $ERROR is empty

然而

$ ./useless.sh 2> >( cat <() > asdf.txt )
This Is Output
$ cat asdf.txt
This Is Error

因此,进程替换通常做正确的事情......不幸的是,每当我用>( )中的东西将QDxswpoi中的STDIN包裹起来试图将其捕获到变量时,我就失去了$()的内容。我认为这是因为$()启动了一个子进程,该进程不再能够访问父进程拥有的/ dev / fd中的文件描述符。

进程替换使我能够处理不再使用STDERR的数据流,遗憾的是我似乎无法以我想要的方式操作它。


0
投票

在zsh中:

$()

0
投票

POSIX

可以使用一些重定向魔法捕获STDERR:

{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )

请注意,命令的STDOUT(此处为$ { 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 )的管道在最里面的ls {内完成。如果您正在执行一个简单的命令(例如,不是管道),您可以删除这些内部括号。

由于管道在}bash中创建子shell,因此无法在命令外部进行管道处理,并且子shell中对变量的赋值将无法用于当前shell。

庆典

zsh中,最好不要假设文件描述符3未使用:

bash

请注意,这在{ error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&$tmp ; } 2>&1); } {tmp}>&1; exec {tmp}>&- # With this syntax the FD stays open 中不起作用。


感谢zsh的一般想法。


0
投票

为了防止您的命令出错:

this answer

execute [INVOKING-FUNCTION] [COMMAND]

灵感来自精益制造:


0
投票

简单的解决方案

Make it obvious to anyone

会产生:

{ ERROR=$(./useless.sh 2>&1 1>&$out); } {out}>&1
echo "-"
echo $ERROR

63
投票

also useless.是

这将允许您通过useless.sh等命令管理sed脚本的输出,并将stderr保存在名为error的变量中。管道的结果将发送到stdout进行显示或通过管道输入另一个命令。

它设置了几个额外的文件描述符来管理执行此操作所需的重定向。

#!/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

56
投票

将stderr重定向到stdout,将stdout重定向到/ dev / null,然后使用反引号或$()捕获重定向的stderr:

ERROR=$(./useless.sh 2>&1 >/dev/null)

8
投票

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

if result=$(useless.sh 2>&1); then
    stdout=$result
else
    rc=$?
    stderr=$result
fi

适用于您希望在成功情况下输出正确或在发生故障时在stderr上发出诊断消息的常见情况。

请注意,shell的控制语句已经检查了引擎盖下的$?;所以看起来像什么

cmd
if [ $? -eq 0 ], then ...

只是一种笨拙,单一的说法

if cmd; then ...

5
投票
# 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}

3
投票

我是这样做的:

#
# $1 - name of the (global) variable where the contents of stderr will be stored
# $2 - command to be executed
#
captureStderr()
{
    local tmpFile=$(mktemp)

    $2 2> $tmpFile

    eval "$1=$(< $tmpFile)"

    rm $tmpFile
}

用法示例:

captureStderr err "./useless.sh"

echo -$err-

它确实使用临时文件。但至少丑陋的东西包含在一个函数中。


2
投票

这是一个有趣的问题,我希望有一个优雅的解决方案。遗憾的是,我最终得到了一个类似于Leffler先生的解决方案,但我要补充一点,你可以在Bash函数中调用无用的东西来提高可读性:

#!/bin/bash

function useless {
    /tmp/useless.sh | sed 's/Output/Useless/'
}

ERROR=$(useless)
echo $ERROR

所有其他类型的输出重定向必须由临时文件支持。


2
投票

为了读者的利益,这里的食谱

  • 可以重复使用作为一个衬垫来将stderr捕获到一个变量中
  • 仍然可以访问命令的返回码
  • 牺牲临时文件描述符3(当然可以由你改变)
  • 并且不会将此临时文件描述符公开给内部命令

如果你想把一些stderrcommand抓到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函数中(可能需要local -n的版本3及以上版本):

: 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>&1stderr重定向到捕获$(..)的输出 1>&3stdout从捕获$(..)的输出重定向回到保存在文件描述符3中的“外部”stdout。注意stderr仍然指向FD 1之前指向的位置:到输出捕获$(..) 然后3>&-关闭文件描述符3,因为它不再需要,这样command不会突然出现一些未知的打开文件描述符。请注意,外壳仍然有FD 3打开,但command将看不到它。 后者很重要,因为像lvm这样的程序会抱怨意外的文件描述符。并且lvmstderr抱怨 - 正是我们要捕获的东西!

如果您相应地调整,您可以使用此配方捕获任何其他文件描述符。当然除了文件描述符1(这里重定向逻辑是错误的,但对于文件描述符1,你可以像往常一样使用var=$(command))。

请注意,这会牺牲文件描述符3.如果您碰巧需要该文件描述符,请随时更改数字。但请注意,有些炮弹(从20世纪80年代开始)可能会将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的前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

这仍然无法保护您免受每次攻击,但至少有助于检测和避免常见的脚本错误。

笔记:

  • catch-var-from-fd-by-fd var 2 3 cmd..catch-stderr var cmd..相同
  • 如果忘记给出正确数量的参数,shift || return只是防止丑陋错误的一种方法。也许终止shell将是另一种方式(但这使得很难从命令行进行测试)。
  • 例程写得这样,它更容易理解。可以重写函数,使其不需要exec,但它会变得非常难看。
  • 此例程也可以为非bash重写,因此不需要local -n。然而,你不能使用局部变量,它变得非常难看!
  • 另请注意,evals以安全的方式使用。通常eval被认为是危险的。然而在这种情况下,它不比使用"$@"(执行任意命令)更邪恶。但请务必使用此处所示的准确和正确的引用(否则它将变得非常危险)。

1
投票
$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr
© www.soinside.com 2019 - 2024. All rights reserved.