假设我有一个像下面这样的脚本:

useless.sh

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

我有另一个shell脚本:

alsoUseless.sh

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

我想捕捉“这是错误”,或任何其他stderr从无用的。sh,到一个变量。 我们称它为ERROR。

注意,我使用了stdout。我想继续使用stdout,所以在这种情况下,将stderr重定向到stdout没有帮助。

所以,基本上,我想做

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

但这显然行不通。

我也知道我能做到

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

但这是丑陋和不必要的。

不幸的是,如果这里没有答案,这就是我要做的。

我希望还有别的办法。

有人有更好的主意吗?


当前回答

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

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

您不能在命令外部进行管道操作,因为管道在bash和zsh中生成了子shell,并且当前shell无法使用对子shell中变量的赋值。

bash

在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

注意,这在zsh中不起作用。


感谢这个大致的答案。

其他回答

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

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

改进YellowApple的回答:

这是一个Bash函数,用于将stderr捕获到任何变量中

stderr_capture_example.sh:

#!/usr/bin/env bash

# Capture stderr from a command to a variable while maintaining stdout
# @Args:
# $1: The variable name to store the stderr output
# $2: Vararg command and arguments
# @Return:
# The Command's Returnn-Code or 2 if missing arguments
function capture_stderr {
  [ $# -lt 2 ] && return 2
  local stderr="$1"
  shift
  {
    printf -v "$stderr" '%s' "$({ "$@" 1>&3; } 2>&1)"
  } 3>&1
}

# Testing with a call to erroring ls
LANG=C capture_stderr my_stderr ls "$0" ''

printf '\nmy_stderr contains:\n%s' "$my_stderr"

测试:

bash stderr_capture_example.sh

输出:

 stderr_capture_example.sh

my_stderr contains:
ls: cannot access '': No such file or directory

此函数可用于捕获返回的对话框命令的选择。

我是这样做的:

#
# $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-

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

我将使用find命令

find / -maxdepth 2 -iname 'tmp' -type d

作为非超级用户的演示。当访问/ dir时,它应该抱怨“权限被拒绝”。

#!/bin/bash

echo "terminal:"
{ err="$(find / -maxdepth 2 -iname 'tmp' -type d 2>&1 1>&3 3>&- | tee /dev/stderr)"; } 3>&1 | tee /dev/fd/4 2>&1; out=$(cat /dev/fd/4)
echo "stdout:" && echo "$out"
echo "stderr:" && echo "$err"

给出输出:

terminal:
find: ‘/root’: Permission denied
/tmp
/var/tmp
find: ‘/lost+found’: Permission denied
stdout:
/tmp
/var/tmp
stderr:
find: ‘/root’: Permission denied
find: ‘/lost+found’: Permission denied

终端输出也包含/dev/stderr内容,就像不带任何脚本地运行find命令一样。$out有/dev/stdout, $err有/dev/stderr内容。

use:

#!/bin/bash

echo "terminal:"
{ err="$(find / -maxdepth 2 -iname 'tmp' -type d 2>&1 1>&3 3>&-)"; } 3>&1 | tee /dev/fd/4; out=$(cat /dev/fd/4)
echo "stdout:" && echo "$out"
echo "stderr:" && echo "$err"

如果您不想在终端输出中看到/dev/stderr。

terminal:
/tmp
/var/tmp
stdout:
/tmp
/var/tmp
stderr:
find: ‘/root’: Permission denied
find: ‘/lost+found’: Permission denied

捕获并打印标准码

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

分解

您可以使用$()来捕获stdout,但您希望捕获stderr。你交换了stdout和stderr。在标准交换算法中使用fd 3作为临时存储。

如果你想捕捉和打印使用tee做一个副本。在这种情况下,tee的输出将被$()捕获,而不是转到控制台,但stderr(的tee)仍然会转到控制台,因此我们使用它作为tee的第二个输出,通过特殊文件/dev/fd/2,因为tee需要一个文件路径而不是fd数。

注意:这是一个可怕的多重定向在单行和顺序问题。$()在管道的末尾抓取tee的stdout,并且管道本身将./useless.sh的stdout路由到tee的stdin,在我们将stdin和stdout交换为./useless.sh之后。

使用./useless.sh的stdout

OP说他仍然想使用(不仅仅是打印)标准输出,比如。/Useless .sh | sed 's/Output/Useless/'。

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

然而,如果你想要捕获标准输出和标准输出,那么我认为你必须回到临时文件,因为$()一次只会做一个,并且它会生成一个子shell,从中你不能返回变量。