假设我有一个如下的脚本:
useless.sh
echo "This Is Error" 1>&2
echo "This Is Output"
我还有另一个shell脚本:
alsoUseless.sh
./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`
但这很丑陋且没必要。
不幸的是,如果没有答案出现在这里,那就是我将要做的事情。
我希望还有另一种方式。
任何人都有更好的想法吗?
答案 0 :(得分: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 :(得分:63)
这将允许您通过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
答案 2 :(得分:52)
将stderr重定向到stdout,将stdout重定向到/ dev / null,然后使用反引号或$()
来捕获重定向的stderr:
ERROR=$(./useless.sh 2>&1 >/dev/null)
答案 3 :(得分:6)
这个问题有很多重复,其中许多都有一个稍微简单的使用场景,你不想捕获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 ...
答案 4 :(得分: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}
答案 5 :(得分: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-
使用临时文件。但至少丑陋的东西包含在一个函数中。
答案 6 :(得分:2)
这是一个有趣的问题,我希望有一个优雅的解决方案。遗憾的是,我最终得到了一个类似于Leffler先生的解决方案,但我要补充一点,你可以在Bash函数中调用无用的东西来提高可读性:
#!/bin/bash function useless { /tmp/useless.sh | sed 's/Output/Useless/' } ERROR=$(useless) echo $ERROR
所有其他类型的输出重定向必须由临时文件支持。
答案 7 :(得分:2)
在Tom Hale's answer上进行一些迭代,我发现可以将重定向瑜伽包装为一个函数,以便于重用。例如:
#!/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。
编辑:capture
函数的替代版本,它将捕获的STDERR输出存储到用户指定的变量中(而不是依赖于全局$captured
),来自Léa Gris's answer的灵感,同时保留了上述实现的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
答案 8 :(得分:1)
$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr
答案 9 :(得分:1)
这篇文章帮助我为自己的目的提出了类似的解决方案:
MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`
然后只要我们的MESSAGE不是空字符串,我们就把它传递给其他东西。如果我们的format_logs.py因某种python异常而失败,这将告诉我们。
答案 10 :(得分:1)
在YellowApple's answer上进行改进:
这是一个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
此功能可用于捕获dialog
命令的返回选择。
答案 11 :(得分: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
的stdout,并且管道本身将./useless.sh
的stdout路由到tee
的stdin,之后我们交换stdin和stdout for { {1}}。
使用./useless.sh的 OP表示他仍然想使用(不仅仅是打印)标准输出,例如 在交换stdout和stderr之前,没问题。我建议将它移动到一个函数或文件(也是--useless.sh)中,并在上面的行中调用它来代替./useless.sh。 但是,如果你想捕获stdout和stderr,那么我认为你必须依赖临时文件,因为./useless.sh
。./useless.sh | sed 's/Output/Useless/'
一次只能做一个并且它创建一个子shell,你不能从中返回变量。
答案 12 :(得分:1)
class Browser(QWebView):
def __init__(self):
# QWebView
self.view = QWebView.__init__(self)
#self.view.setPage(MyBrowser())
self.setWindowTitle('Loading...')
self.titleChanged.connect(self.adjustTitle)
self.showFullScreen()
会产生:
{ ERROR=$(./useless.sh 2>&1 1>&$out); } {out}>&1
echo "-"
echo $ERROR
答案 13 :(得分:0)
我将使用 find
命令
find / -maxdepth 2 -iname 'tmp' -type d
作为演示的非超级用户。访问 /
目录时,它应该抱怨“权限被拒绝”。
#!/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
terminal
输出也包含 /dev/stderr
内容,就像您在没有任何脚本的情况下运行该 find 命令一样。 $out
有 /dev/stdout
,$err
有 /dev/stderr
内容。
使用:
#!/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
答案 14 :(得分:0)
为了读者的利益,此食谱在这里
如果您想将stderr
中的command
捕获到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在此处保存标准输出点{ command; }
(或“ $ @”)然后在输出捕获$(..)
中执行命令2>&1
将stderr
重定向到捕获$(..)
的输出1>&3
将stdout
从输出捕获$(..)
重定向回到保存在文件描述符3中的“外部” stdout
。请注意,stderr
仍然指的是FD 1指向的位置:到捕获$(..)
3>&-
然后关闭文件描述符3,因为不再需要它,这样command
不会突然出现一些未知的打开文件描述符。请注意,外壳仍然打开了FD 3,但是command
将看不到它。lvm
之类的某些程序会抱怨意外的文件描述符。 lvm
向stderr
抱怨-正是我们要捕获的东西!如果进行相应的调整,则可以使用此配方捕获其他任何文件描述符。当然不包括文件描述符1(这里的重定向逻辑是错误的,但是对于文件描述符1,您可以照常使用var=$(command)
)。
请注意,这会牺牲文件描述符3。如果您碰巧需要该文件描述符,请随时更改数字。但是请注意,某些外壳程序(从1980年代开始)可能会将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
的前三个参数。始终以“静态”方式显式地给它们。所以,不,不,
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
仅是防止难看的错误的一种方法,以防万一您忘记提供正确数量的参数。也许终止外壳是另一种方式(但这使从命令行进行测试变得困难)。exec
,但是它真的很难看。bash
重写此例程,从而不需要local -n
。但是,那么您将无法使用局部变量,并且它变得非常丑陋!eval
是安全使用的。通常eval
被认为是危险的。但是,在这种情况下,它仅比使用"$@"
(执行任意命令)更邪恶。但是,请务必使用此处显示的正确和正确的引用(否则它会变得非常非常危险)。答案 15 :(得分:0)
可以使用一些重定向魔术来捕获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
请注意,命令(此处为ls
)的STDOUT的传递是在最里面的{
}
内部完成的。如果您正在执行一个简单的命令(例如,不是管道),则可以删除这些内部括号。
由于管道在bash
和zsh
中创建了一个子外壳,因此您不能在命令之外进行管道传输,并且该子外壳中的变量分配对当前外壳不可用。
在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
。
感谢this answer的一般想法。
答案 16 :(得分:0)
防错您的命令:
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
}
精益制造中的灵感:
答案 17 :(得分:0)
在zsh中:
{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )
答案 18 :(得分: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
因此,进程替换正在做一般正确的事情......不幸的是,每当我用>( )
中的ST $()
中的STDIN包围,试图将其捕获到变量,我丢失了$()
的内容。我认为这是因为$()
启动了一个子进程,该进程不再能够访问父进程拥有的/ dev / fd中的文件描述符。
进程替换使我能够使用不再使用STDERR的数据流,遗憾的是我似乎无法以我想要的方式操作它。