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
.
警告:正式未经测试的代码 - 使用风险自负.
Pau*_*ce. 64
这将允许您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)
Cha*_*ens 59
将stderr重定向到stdout,将stdout重定向到/ dev/null,然后使用反引号或$()
捕获重定向的stderr:
ERROR=$(./useless.sh 2>&1 >/dev/null)
Run Code Online (Sandbox Code Playgroud)
Tin*_*ino 12
为了读者的利益,这个食谱在这里
如果你想赶上stderr
一些command
到var
你可以做
{ 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>&3
stdout
从输出捕获重定向$(..)
回stdout
保存在文件描述符 3 中的“外部” 。请注意,stderr
仍然指之前 FD 1 指向的位置:到输出捕获$(..)
3>&-
然后关闭文件描述符 3,因为它不再需要,这样command
就不会突然出现一些未知的打开文件描述符。请注意,外壳仍然打开 FD 3,但command
不会看到它。lvm
抱怨意外的文件描述符。并lvm
抱怨stderr
- 正是我们要捕捉的!如果您相应地进行调整,您可以使用此配方捕获任何其他文件描述符。当然,除了文件描述符 1(这里的重定向逻辑是错误的,但是对于文件描述符 1,您可以var=$(command)
照常使用)。
请注意,这会牺牲文件描述符 3。如果您碰巧需要该文件描述符,请随时更改编号。但请注意,某些 shell(来自 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>&-";
}
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
被认为是危险的。然而,在这种情况下,它并不比使用"$@"
(执行任意命令)更邪恶。但是,请务必使用此处显示的准确和正确的引用(否则会变得非常危险)。这个问题有很多重复,其中许多都有一个稍微简单的使用场景,你不想同时捕获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)
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
.
感谢这个答案的总体思路。
{ 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)