假设我有一个像下面这样的脚本:
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`
但这是丑陋和不必要的。
不幸的是,如果这里没有答案,这就是我要做的。
我希望还有别的办法。
有人有更好的主意吗?
重复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
我将使用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
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中不起作用。
感谢这个大致的答案。