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

psy*_*ca0 164 variables bash shell redirect stderr

假设我有一个如下脚本:

useless.sh

echo "This Is Error" 1>&2
echo "This Is Output" 
Run Code Online (Sandbox Code Playgroud)

我有另一个shell脚本:

alsoUseless.sh

./useless.sh | sed 's/Output/Useless/'
Run Code Online (Sandbox Code Playgroud)

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

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

所以,基本上,我想做

./useless.sh 2> $ERROR | ...
Run Code Online (Sandbox Code Playgroud)

但这显然不起作用.

我也知道我能做到

./useless.sh 2> /tmp/Error
ERROR=`cat /tmp/Error`
Run Code Online (Sandbox Code Playgroud)

但那是丑陋和不必要的.

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

我希望还有另一种方式.

有没有更好的想法?

Jon*_*ler 80

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

ERROR=$(</tmp/Error)
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

  • 如果你不需要标准输出,你可以将它重定向到`/ dev/null`而不是`outfile`(如果你像我一样,你通过Google发现了这个问题,并且没有相同的要求OP) (9认同)
  • 有关没有临时文件的答案,请参阅[此处](/sf/answers/3681155761/). (2认同)

Pau*_*ce. 64

alsoUseless.sh

这将允许您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
Run Code Online (Sandbox Code Playgroud)

  • 使用'exec'设置和关闭文件描述符是一种很好的技巧.如果脚本随后立即退出,则不需要关闭. (4认同)
  • 我如何在变量中捕获`stderr`和`stdout`? (3认同)
  • 可能更简单,有:error = $(./ useless.sh | sed's/Output/Useless /'2>&1 1>&3) (2认同)

Cha*_*ens 59

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

ERROR=$(./useless.sh 2>&1 >/dev/null)
Run Code Online (Sandbox Code Playgroud)

  • 这就是我在我的例子中包含管道的原因.我仍然想要标准输出,我希望它做其他事情,去其他地方. (7认同)

Tin*_*ino 12

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

  • 可以重新用作 oneliner 将 stderr 捕获到变量中
  • 仍然可以访问命令的返回代码
  • 牺牲一个临时文件描述符 3(当然你可以改变)
  • 并且不会将此临时文件描述符暴露给内部命令

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

{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Run Code Online (Sandbox Code Playgroud)

之后你就拥有了一切:

echo "command gives $? and stderr '$var'";
Run Code Online (Sandbox Code Playgroud)

如果command很简单(不是类似的东西a | b),你可以离开内部{}

{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
Run Code Online (Sandbox Code Playgroud)

包装成一个简单的可重用bash函数(可能需要版本 3 及更高版本local -n):

: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
Run Code Online (Sandbox Code Playgroud)

解释:

  • 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- 正是我们要捕捉的!

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

请注意,这会牺牲文件描述符 3。如果您碰巧需要该文件描述符,请随时更改编号。但请注意,某些 shell(来自 1980 年代)可能会理解99>&19后跟的参数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>&-";
}
Run Code Online (Sandbox Code Playgroud)

安全说明: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

这仍然不能保护您免受每个漏洞的侵害,但至少有助于检测和避免常见的脚本错误。

笔记:

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


tri*_*eee 8

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

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

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

请注意,shell的控制语句已$?在底层检查; 所以看起来像什么

cmd
if [ $? -eq 0 ], then ...
Run Code Online (Sandbox Code Playgroud)

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

if cmd; then ...
Run Code Online (Sandbox Code Playgroud)


Tom*_*ale 6

POSIX

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
Run Code Online (Sandbox Code Playgroud)

请注意,命令的 STDOUT 管道(此处ls)是在最里面完成的{ }。如果您正在执行一个简单的命令(例如,不是管道),您可以删除这些内部大括号。

您无法在命令之外进行管道传输,因为管道在bash和中创建了一个子 shell zsh,并且对子 shell 中变量的赋值对当前 shell 不可用。

巴什

在 中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
Run Code Online (Sandbox Code Playgroud)

请注意,这在zsh.


感谢这个答案的总体思路。

  • @ThiagoConrado我认为在这种情况下“tmp”只是一个变量,用于存储您知道未使用的文件描述符。例如,如果“tmp=3”,则“1&gt;&amp;$tmp”将变为“1&gt;&amp;3”,并且命令将与前面解释的相同(它将在文件描述符中存储“stdout”(“1”) `3` 比 `stderr` (`2`) 会转到 `stdout` 并存储在 `error` 变量中,最后流式传输到文件描述符 `3` 的内容返回到文件描述符 `1` ,即“stdout”,因为“{tmp}&gt;&amp;1”会变成“3&gt;&amp;1”,如果我理解正确的话)。 (2认同)

Kar*_*son 6

一个简单的解决方案

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

将产生:

This Is Output
-
This Is Error
Run Code Online (Sandbox Code Playgroud)


小智 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}
Run Code Online (Sandbox Code Playgroud)

  • `command` 在这里是一个糟糕的选择,因为实际上有一个同名的内置函数。可能会使其成为“yourCommand”之类的,更明确。 (2认同)