假设我有一个如下脚本:
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`
但那是丑陋和不必要的。
不幸的是,如果没有答案出现在这里,那就是我将要做的事情。
我希望还有另一种方式。
有没有更好的想法?
因此捕获错误文件会更简洁:
ERROR=$(</tmp/Error)
shell识别出这一点,并且不必运行'cat
'来获取数据。
更大的问题很难。我认为没有一种简单的方法可以做到这一点。您必须将整个管道构建到子shell中,最终将其最终标准输出发送到文件,以便您可以将错误重定向到标准输出。
ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )
请注意,需要使用分号(在经典的shell中 - Bourne,Korn - 肯定;也可能在Bash中)。 '{}
'对所附命令执行I / O重定向。如上所述,它也会捕获来自sed
的错误。
警告:正式未经测试的代码 - 使用风险自负。
这篇文章帮助我为自己的目的提出了类似的解决方案:
MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`
然后只要我们的MESSAGE不是空字符串,我们就把它传递给其他东西。如果我们的format_logs.py因某种python异常而失败,这将告诉我们。
捕获并打印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,你不能从中返回变量。
如果您想绕过临时文件的使用,您可以使用进程替换。我还没有完全开始工作。这是我的第一次尝试:
$ .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的数据流,遗憾的是我似乎无法以我想要的方式操作它。
在zsh中:
$()
可以使用一些重定向魔法捕获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
的一般想法。
为了防止您的命令出错:
this answer
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
}
Make it obvious to anyone
会产生:
{ ERROR=$(./useless.sh 2>&1 1>&$out); } {out}>&1
echo "-"
echo $ERROR
这将允许您通过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
将stderr重定向到stdout,将stdout重定向到/ dev / null,然后使用反引号或$()
捕获重定向的stderr:
ERROR=$(./useless.sh 2>&1 >/dev/null)
这个问题有很多重复,其中许多都有一个稍微简单的使用场景,你不想同时捕获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 ...
# 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}
我是这样做的:
#
# $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-
它确实使用临时文件。但至少丑陋的东西包含在一个函数中。
这是一个有趣的问题,我希望有一个优雅的解决方案。遗憾的是,我最终得到了一个类似于Leffler先生的解决方案,但我要补充一点,你可以在Bash函数中调用无用的东西来提高可读性:
#!/bin/bash function useless { /tmp/useless.sh | sed 's/Output/Useless/' } ERROR=$(useless) echo $ERROR
所有其他类型的输出重定向必须由临时文件支持。
为了读者的利益,这里的食谱
如果你想把一些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来保存stdout点{ 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.如果您碰巧需要该文件描述符,请随时更改数字。但请注意,有些炮弹(从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
。然而,你不能使用局部变量,它变得非常难看!eval
s以安全的方式使用。通常eval
被认为是危险的。然而在这种情况下,它不比使用"$@"
(执行任意命令)更邪恶。但请务必使用此处所示的准确和正确的引用(否则它将变得非常危险)。$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr