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

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`

但这是丑陋和不必要的。

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

我希望还有别的办法。

有人有更好的主意吗?


当前回答

在zsh:

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

其他回答

重复Tom Hale的回答,我发现可以将重定向瑜伽包装到一个函数中,以便于重用。例如:

#!/bin/sh

capture () {
    { captured=$( { { "$@" ; } 1>&3 ; } 2>&1); } 3>&1
}

# Example usage; capturing dialog's output without resorting to temp files
# was what motivated me to search for this particular SO question
capture dialog --menu "Pick one!" 0 0 0 \
        "FOO" "Foo" \
        "BAR" "Bar" \
        "BAZ" "Baz"
choice=$captured

clear; echo $choice

几乎可以肯定,可以进一步简化。还没有进行特别彻底的测试,但它似乎可以同时使用bash和ksh。


EDIT:捕获函数的另一个版本,它将捕获的STDERR输出存储到用户指定的变量中(而不是依赖于全局$ capturing),灵感来自Léa Gris的答案,同时保留了上述实现的ksh(和zsh)兼容性:

capture () {
    if [ "$#" -lt 2 ]; then
        echo "Usage: capture varname command [arg ...]"
        return 1
    fi
    typeset var captured; captured="$1"; shift
    { read $captured <<<$( { { "$@" ; } 1>&3 ; } 2>&1); } 3>&1
}

和用法:

capture choice dialog --menu "Pick one!" 0 0 0 \
        "FOO" "Foo" \
        "BAR" "Bar" \
        "BAZ" "Baz"

clear; echo $choice

为了防止你的命令出错:

execute [INVOKING-FUNCTION] [COMMAND]

execute () {
    function="${1}"
    command="${2}"
    error=$(eval "${command}" 2>&1 >"/dev/null")

    if [ ${?} -ne 0 ]; then
        echo "${function}: ${error}"
        exit 1
    fi
}

精益生产的启示:

故意让错误变得不可能 让步骤最小 逐个完成项目 让每个人都清楚

这个问题有很多重复的地方,其中许多都有一个稍微简单的使用场景,您不希望同时捕获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 ...

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

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

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中不起作用。


感谢这个大致的答案。