bash 将 stdout 和 stderr 捕获到不同的变量中
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11027679/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Capture stdout and stderr into different variables
提问by ntc2
Is it possible to store or capture stdout and stderr in different variables, without using a temp file? Right now I do this to get stdout in out
and stderr in err
when running some_command
, but I'd
like to avoid the temp file.
是否可以在不使用临时文件的情况下在不同变量中存储或捕获 stdout 和 stderr ?现在我这样做是为了out
在err
运行时获取 stdout和 stderr some_command
,但我想避免使用临时文件。
error_file=$(mktemp)
out=$(some_command 2>$error_file)
err=$(< error_file)
rm $error_file
采纳答案by TheConstructor
Ok, it got a bit ugly, but here is a solution:
好吧,它有点难看,但这里有一个解决方案:
unset t_std t_err
eval "$( (echo std; echo err >&2) \
2> >(readarray -t t_err; typeset -p t_err) \
> >(readarray -t t_std; typeset -p t_std) )"
where (echo std; echo err >&2)
needs to be replaced by the actual command. Output of stdoutis saved into the array $t_std
line by line omitting the newlines (the -t
) and stderrinto $t_err
.
where(echo std; echo err >&2)
需要由实际命令替换。的输出标准输出被保存到数组中$t_std
由线省略换行符(线-t
)和标准错误入$t_err
。
If you don't like arrays you can do
如果你不喜欢数组,你可以做
unset t_std t_err
eval "$( (echo std; echo err >&2 ) \
2> >(t_err=$(cat); typeset -p t_err) \
> >(t_std=$(cat); typeset -p t_std) )"
which pretty much mimics the behavior of var=$(cmd)
except for the value of $?
which takes us to the last modification:
这几乎模仿了var=$(cmd)
除了$?
which的值将我们带到最后一次修改的行为:
unset t_std t_err t_ret
eval "$( (echo std; echo err >&2; exit 2 ) \
2> >(t_err=$(cat); typeset -p t_err) \
> >(t_std=$(cat); typeset -p t_std); t_ret=$?; typeset -p t_ret )"
Here $?
is preserved into $t_ret
这里$?
保存成$t_ret
Tested on Debian wheezy using GNUbash
, Version 4.2.37(1)-release (i486-pc-linux-gnu).
在 Debian wheezy 上使用GNU 进行测试bash
,版本4.2.37(1)-release (i486-pc-linux-gnu)。
回答by Tino
This is for catching stdout and stderr in different variables. If you only want to catch
stderr
, leavingstdout
as-is, there is a better and shorter solution.
这是为了在不同的变量中捕获 stdout 和 stderr。 如果您只想 catch
stderr
,保持stdout
原样,有一个更好更短的解决方案。
To sumeverything upfor the benefit of the reader, here is an
Easy Reusable bash
Solution
易于重复使用的bash
解决方案
This version does use subshells and runs without tempfile
s. (For a tempfile
version which runs without subshells, see my other answer.)
这个版本确实使用了子外壳并且在没有tempfile
s 的情况下运行。(对于tempfile
没有子外壳运行的版本,请参阅我的其他答案。)
: catch STDOUT STDERR cmd args..
catch()
{
eval "$({
__2="$(
{ __1="$("${@:3}")"; } 2>&1;
ret=$?;
printf '%q=%q\n' "" "$__1" >&2;
exit $ret
)"
ret="$?";
printf '%s=%q\n' "" "$__2" >&2;
printf '( exit %q )' "$ret" >&2;
} 2>&1 )";
}
Example use:
使用示例:
dummy()
{
echo "" >&2
echo "" >&1
return ""
}
catch stdout stderr dummy 3 $'\ndiffcult\n data \n\n\n' $'\nother\n difficult \n data \n\n'
printf 'ret=%q\n' "$?"
printf 'stdout=%q\n' "$stdout"
printf 'stderr=%q\n' "$stderr"
this prints
这打印
ret=3
stdout=$'\ndiffcult\n data '
stderr=$'\nother\n difficult \n data '
So it can be used without deeper thinking about it. Just put catch VAR1 VAR2
in front of any command args..
and you are done.
所以不用深入思考就可以使用。只要放在catch VAR1 VAR2
any 前面,command args..
你就完成了。
Some if cmd args..; then
will become if catch VAR1 VAR2 cmd args..; then
. Really nothing complex.
有些if cmd args..; then
会变成if catch VAR1 VAR2 cmd args..; then
. 真的没有什么复杂的。
Discussion
讨论
Q: How does it work?
问:它是如何工作的?
It just wraps ideas from the other answers here into a function, such that it can easily be resused.
它只是将这里其他答案的想法包装到一个函数中,以便可以轻松地重用它。
catch()
basically uses eval
to set the two variables. This is similar to https://stackoverflow.com/a/18086548
catch()
基本上用于eval
设置两个变量。这类似于https://stackoverflow.com/a/18086548
Consider a call of catch out err dummy 1 2a 3b
:
考虑一个调用catch out err dummy 1 2a 3b
:
let's skip the
eval "$({
and the__2="$(
for now. I will come to this later.__1="$("$("${@:3}")"; } 2>&1;
executesdummy 1 2 3
and stores it'sstdout
into__1
for later use. So__1
becomes2a
. It also redirectsstderr
ofdummy
tostdout
, such that the outer catch can gatherstdout
ret=$?;
catches the exit code, which is1
printf '%q=%q\n' "$1" "$__1" >&2;
then outputsout=2a
tostderr
.stderr
is used here, as the currentstdout
already has taken over the role ofstderr
of thedummy
command.exit $ret
then forwards the exit code (1
) to the next stage.
让我们暂时跳过
eval "$({
和__2="$(
。我稍后会谈到这个。__1="$("$("${@:3}")"; } 2>&1;
执行dummy 1 2 3
并将它stdout
变成__1
供以后使用。于是__1
变成2a
。它还重定向stderr
的dummy
到stdout
,使得外键锁可以收集stdout
ret=$?;
捕获退出代码,即1
printf '%q=%q\n' "$1" "$__1" >&2;
然后输出out=2a
到stderr
.stderr
在这里使用,因为目前stdout
已接管的作用stderr
的的dummy
命令。exit $ret
然后将退出代码 (1
)转发到下一阶段。
Now to the outer __2="$( ... )"
:
现在到外部__2="$( ... )"
:
This catches
stdout
of the above, which is thestderr
of thedummy
call, into variable__2
. (We could re-use__1
here, but I used__2
to make it less confusing.). So__2
becomes3b
ret="$?";
catches the (returned) return code1
(fromdummy
) againprintf '%s=%q\n' "$2" "$__2" >&2;
then outputserr=3a
tostderr
.stderr
is used again, as it already was used to output the other variableout=2a
.printf '( exit %q )' "$ret" >&2; then outputs the code to set the proper return value. I did not find a better way, as assignig it to a variable needs a variable name, which then cannot be used as first oder second argument to
catch`.
此捕获
stdout
以上,这是的stderr
所述的dummy
呼叫,为可变__2
。(我们可以__1
在这里重复使用,但我曾经__2
让它不那么令人困惑。)。于是__2
变成3b
ret="$?";
再次捕获(返回的)返回码1
(fromdummy
)printf '%s=%q\n' "$2" "$__2" >&2;
然后输出err=3a
到stderr
.stderr
再次使用,因为它已经用于输出另一个变量out=2a
。printf '( exit %q )' "$ret" >&2; then outputs the code to set the proper return value. I did not find a better way, as assignig it to a variable needs a variable name, which then cannot be used as first oder second argument to
赶上`。
Please note that, as an optimization, we could have written those 2 printf
as a single one like printf '%s=%q\n( exit %q )
"$__2" "$ret"` as well.
请注意,作为一种优化,我们也可以将这 2 个写成printf
一个,例如printf '%s=%q\n( exit %q )
"$__2" "$ret"`。
So what do we have so far?
那么到目前为止我们有什么?
We have following written to stderr:
我们将以下内容写入 stderr:
out=2a
err=3b
( exit 1 )
where out
is from $1
, 2a
is from stdout
of dummy
, err
is from $2
, 3b
is from stderr
of dummy
, and the 1
is from the return code from dummy
.
其中out
来自$1
,2a
来自stdout
于dummy
,err
来自于$2
,3b
来自stderr
于dummy
,1
来自于返回码来自dummy
。
Please note that %q
in the format of printf
takes care for quoting, such that the shell sees proper (single) arguments when it comes to eval
. 2a
and 3b
are so simple, that they are copied literally.
请注意,%q
在 的格式中printf
注意引用,以便外壳在涉及eval
. 2a
并且3b
非常简单,以至于它们被逐字复制。
Now to the outer eval "$({ ... } 2>&1 )";
:
现在到外部eval "$({ ... } 2>&1 )";
:
This executes all of above which output the 2 variables and the exit
, catches it (therefor the 2>&1
) and parses it into the current shell using eval
.
这将执行以上所有输出 2 个变量和exit
,捕获它(因此2>&1
)并使用eval
.
This way the 2 variables get set and the return code as well.
这样就可以设置 2 个变量和返回码。
Q: It uses eval
which is evil. So is it safe?
问:它使用eval
哪个是邪恶的。那么它安全吗?
- As long as
printf %q
has no bugs, it should be safe. But you always have to be very careful, just think about ShellShock.
- 只要
printf %q
没有bug,应该是安全的。但是你总是要非常小心,想想ShellShock。
Q: Bugs?
问:错误?
No obvious bugs are known, except following:
- Catching big output needs big memory and CPU, as everything goes into variables and needs to be back-parsed by the shell. So use it wisely.
As usual
$(echo $'\n\n\n\n')
swallows all linefeeds, not only the last one. This is a POSIX requirement. If you need to get the LFs unharmed, just add some trailing character to the output and remove it afterwards like in following recipe (look at the trailingx
which allows to read a softlink pointing to a file which ends on a$'\n'
):target="$(readlink -e "$file")x" target="${target%x}"
Shell-variables cannot carry the byte NUL (
$'\0'
). They are simply ignores if they happen to occur instdout
orstderr
.
The given command runs in a sub-subshell. So it has no access to
$PPID
, nor can it alter shell variables. You cancatch
a shell function, even builtins, but those will not be able to alter shell variables (as everything running within$( .. )
cannot do this). So if you need to run a function in current shell and catch it's stderr/stdout, you need to do this the usual way withtempfile
s. (There are ways to do this such, that interrupting the shell normally does not leave debris behind, but this is complex and deserves it's own answer.)
没有明显的错误是已知的,除了以下几点:
- 捕获大输出需要大内存和 CPU,因为所有内容都进入变量并需要由 shell 进行反向解析。所以要明智地使用它。
像往常一样
$(echo $'\n\n\n\n')
吞下所有换行符,而不仅仅是最后一个。这是 POSIX 要求。如果您需要让 LF 不受伤害,只需在输出中添加一些尾随字符,然后像以下配方中那样将其删除(查看尾随x
,它允许读取指向以 a 结尾的文件的软链接$'\n'
):target="$(readlink -e "$file")x" target="${target%x}"
Shell 变量不能携带字节 NUL (
$'\0'
)。如果它们碰巧发生在stdout
或 中,则它们只是被忽略stderr
。
给定的命令在子子外壳中运行。所以它不能访问
$PPID
,也不能改变 shell 变量。您可以catch
使用 shell 函数,甚至是内置函数,但这些函数将无法更改 shell 变量(因为在其中运行的所有内容$( .. )
都不能这样做)。因此,如果您需要在当前 shell 中运行一个函数并捕获它的 stderr/stdout,则需要使用tempfile
s以通常的方式执行此操作。(有一些方法可以做到这一点,中断外壳通常不会留下碎片,但这很复杂,值得自己回答。)
Q: Bash version?
问:Bash 版本?
- I think you need Bash 4 and above (due to
printf %q
)
- 我认为您需要 Bash 4 及更高版本(由于
printf %q
)
Q: This still looks so awkward.
问:这看起来还是很尴尬。
- Right. Another answer hereshows how it can be done in
ksh
much more cleanly. However I am not used toksh
, so I leave it to others to create a similar easy to reuse recipe forksh
.
- 对。 这里的另一个答案显示了如何
ksh
更干净地完成它。但是我不习惯ksh
,所以我把它留给其他人来创建一个类似的易于重用的食谱ksh
。
Q: Why not use ksh
then?
问:那为什么不使用ksh
呢?
- Because this is a
bash
solution
- 因为这是一个
bash
解决方案
Q: The script can be improved
问:脚本可以改进
- Of course you can squeeze out some bytes and create smaller or more incomprehensible solution. Just go for it ;)
- 当然,您可以挤出一些字节并创建更小或更难以理解的解决方案。就去吧;)
Q: There is a typo. : catch STDOUT STDERR cmd args..
shall read # catch STDOUT STDERR cmd args..
问:有错别字。 : catch STDOUT STDERR cmd args..
应阅读# catch STDOUT STDERR cmd args..
- Actually this is intended.
:
shows up inbash -x
while comments are silently swallowed. So you can see where the parser is if you happen to have a typo in the function definition. It's an old debugging trick. But beware a bit, you can easily create some neat sideffects within the arguments of:
.
- 其实这是有意为之。
:
出现在bash -x
评论被默默吞下的时候。因此,如果函数定义中碰巧有拼写错误,您可以看到解析器的位置。这是一个古老的调试技巧。但请注意,您可以轻松地在:
.
Edit: Added a couple more ;
to make it more easy to create a single-liner out of catch()
. And added section how it works.
编辑:添加了更多内容;
,以便更轻松地从catch()
. 并添加了它是如何工作的部分。
回答by ormaaj
Jonathan has the answer. For reference, this is the ksh93 trick. (requires a non-ancient version).
乔纳森有答案。作为参考,这是 ksh93 技巧。(需要非古代版本)。
function out {
echo stdout
echo stderr >&2
}
x=${ { y=$(out); } 2>&1; }
typeset -p x y # Show the values
produces
产生
x=stderr
y=stdout
The ${ cmds;}
syntax is just a command substitution that doesn't create a subshell. The commands are executed in the current shell environment. The space at the beginning is important ({
is a reserved word).
该${ cmds;}
语法只是一个命令替换不创建一个子shell。命令在当前 shell 环境中执行。开头的空格很重要({
是保留字)。
Stderr of the inner command group is redirected to stdout (so that it applies to the inner substitution). Next, the stdout of out
is assigned to y
, and the redirected stderr is captured by x
, without the usual loss of y
to a command substitution's subshell.
内部命令组的 Stderr 被重定向到 stdout(以便它适用于内部替换)。接下来,将 的标准输出out
分配给y
,重定向的标准错误由 捕获x
,而不会丢失y
命令替换的子shell。
It isn't possible in other shells, because all constructs which capture output require putting the producer into a subshell, which in this case, would include the assignment.
这在其他 shell 中是不可能的,因为所有捕获输出的构造都需要将生产者放入子 shell 中,在这种情况下,它将包含赋值。
update:Now also supported by mksh.
更新:现在也支持 mksh。
回答by ormaaj
This command sets both stdout (stdval) and stderr (errval) values in the present running shell:
此命令在当前运行的 shell 中设置 stdout (stdval) 和 stderr (errval) 值:
eval "$( execcommand 2> >(setval errval) > >(setval stdval); )"
provided this function has been defined:
如果已定义此函数:
function setval { printf -v "" "%s" "$(cat)"; declare -p ""; }
Change execcommand to the captured command, be it "ls", "cp", "df", etc.
将 execcommand 更改为捕获的命令,可以是“ls”、“cp”、“df”等。
All this is based on the idea that we could convert all captured values to a text line with the help of the function setval, then setval is used to capture each value in this structure:
所有这一切都是基于这样一个想法,即我们可以在函数 setval 的帮助下将所有捕获的值转换为文本行,然后 setval 用于捕获此结构中的每个值:
execcommand 2> CaptureErr > CaptureOut
Convert each capture value to a setval call:
将每个捕获值转换为 setval 调用:
execcommand 2> >(setval errval) > >(setval stdval)
Wrap everything inside an execute call and echo it:
将所有内容包装在执行调用中并回显它:
echo "$( execcommand 2> >(setval errval) > >(setval stdval) )"
You will get the declare calls that each setval creates:
您将获得每个 setval 创建的声明调用:
declare -- stdval="I'm std"
declare -- errval="I'm err"
To execute that code (and get the vars set) use eval:
要执行该代码(并获取 vars 集),请使用 eval:
eval "$( execcommand 2> >(setval errval) > >(setval stdval) )"
and finally echo the set vars:
最后回显设置的变量:
echo "std out is : |$stdval| std err is : |$errval|
It is also possible to include the return (exit) value.
A complete bash script example looks like this:
还可以包括返回(退出)值。
一个完整的 bash 脚本示例如下所示:
#!/bin/bash --
# The only function to declare:
function setval { printf -v "" "%s" "$(cat)"; declare -p ""; }
# a dummy function with some example values:
function dummy { echo "I'm std"; echo "I'm err" >&2; return 34; }
# Running a command to capture all values
# change execcommand to dummy or any other command to test.
eval "$( dummy 2> >(setval errval) > >(setval stdval); <<<"$?" setval retval; )"
echo "std out is : |$stdval| std err is : |$errval| return val is : |$retval|"
回答by Irfy
Technically, named pipes aren't temporary files and nobody here mentions them. They store nothing in the filesystem and you can delete them as soon as you connect them (so you won't ever see them):
从技术上讲,命名管道不是临时文件,这里没有人提到它们。它们在文件系统中不存储任何内容,您可以在连接它们后立即删除它们(因此您永远不会看到它们):
#!/bin/bash -e
foo () {
echo stdout1
echo stderr1 >&2
sleep 1
echo stdout2
echo stderr2 >&2
}
rm -f stdout stderr
mkfifo stdout stderr
foo >stdout 2>stderr & # blocks until reader is connected
exec {fdout}<stdout {fderr}<stderr # unblocks `foo &`
rm stdout stderr # filesystem objects are no longer needed
stdout=$(cat <&$fdout)
stderr=$(cat <&$fderr)
echo $stdout
echo $stderr
exec {fdout}<&- {fderr}<&- # free file descriptors, optional
You can have multiple background processes this way and asynchronously collect their stdouts and stderrs at a convenient time, etc.
您可以通过这种方式拥有多个后台进程,并在方便的时间异步收集它们的 stdout 和 stderr,等等。
If you need this for one process only, you may just as well use hardcoded fd numbers like 3 and 4, instead of the {fdout}/{fderr}
syntax (which finds a free fd for you).
如果你只需要一个进程,你也可以使用硬编码的 fd 数字,比如 3 和 4,而不是{fdout}/{fderr}
语法(它会为你找到一个免费的 fd)。
回答by madmurphy
I think before saying “you can't” do something, people should at least give it a try with their own hands…
我想在说“你不能”做某事之前,人们至少应该亲自尝试一下……
Simple and clean solution, without using eval
or anything exotic
简单干净的解决方案,无需使用eval
或任何异国情调
1. A minimal version
1. 最小版本
{
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
} < <((printf '#!/bin/bash
#
# useless.sh
#
echo "This is stderr" 1>&2
echo "This is stdout"
%s#!/bin/bash
#
# capture.sh
#
{
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
} < <((printf 'Here is the captured stdout:
This is stdout
And here is the captured stderr:
This is stderr
%s(printf 'IFS=$'\n' read -r -d '' CAPTURED_STDERR;
%sIFS=$'\n' read -r -d '' CAPTURED_STDOUT;
' "$(some_command)" 1>&2) 2>&1
' "$(./useless.sh)" 1>&2) 2>&1)
echo 'Here is the captured stdout:'
echo "${CAPTURED_STDOUT}"
echo
echo 'And here is the captured stderr:'
echo "${CAPTURED_STDERR}"
echo
' "$(some_command)" 1>&2) 2>&1)
Requires:printf
, read
要求:printf
,read
2. A simple test
2. 一个简单的测试
A dummy script for producing stdout
and stderr
: useless.sh
用于生成stdout
和的虚拟脚本stderr
:useless.sh
{
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
} < <((printf '{
IFS= read -r -d '' CAPTURED_STDOUT;
IFS= read -r -d '' CAPTURED_STDERR;
(IFS= read -r -d '' CAPTURED_EXIT; exit "${CAPTURED_EXIT}");
} < <((({ { some_command ; echo "${?}" 1>&3; } | tr -d '{
IFS= read -r -d '' CAPTURED_STDOUT;
IFS= read -r -d '' CAPTURED_STDERR;
(IFS= read -r -d '' CAPTURED_EXIT; exit "${CAPTURED_EXIT}");
} < <((({ { some_command ; echo "${?}" 1>&3; } | tr -d '{
IFS= read -r -d '' CAPTURED_STDERR;
IFS= read -r -d '' CAPTURED_EXIT;
IFS= read -r -d '' CAPTURED_STDOUT;
} < <((printf '{
IFS= read -r -d '' CAPTURED_STDERR;
IFS= read -r -d '' CAPTURED_EXIT;
IFS= read -r -d '' CAPTURED_STDOUT;
} < <((printf '{
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
(IFS=$'\n' read -r -d '' _ERRNO_; exit ${_ERRNO_});
} < <((printf '{
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
(IFS=$'\n' read -r -d '' _ERRNO_; exit ${_ERRNO_});
} < <((printf '# SYNTAX:
# catch STDOUT_VARIABLE STDERR_VARIABLE COMMAND
catch() {
{
IFS=$'\n' read -r -d '' "";
IFS=$'\n' read -r -d '' "";
(IFS=$'\n' read -r -d '' _ERRNO_; return ${_ERRNO_});
} < <((printf 'catch MY_STDOUT MY_STDERR './useless.sh'
echo "The \`./useless.sh\` program exited with code ${?}"
echo
echo 'Here is the captured stdout:'
echo "${MY_STDOUT}"
echo
echo 'And here is the captured stderr:'
echo "${MY_STDERR}"
echo
%sThe `./useless.sh` program exited with code 0
Here is the captured stdout:
This is stderr 1
This is stderr 2
And here is the captured stderr:
This is stdout 1
This is stdout 2
%d_CTRL_Z_=$'\cZ'
{
IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" CAPTURED_STDERR;
IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" CAPTURED_STDOUT;
(IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" _ERRNO_; exit ${_ERRNO_});
} <<EOF
$((printf "${_CTRL_Z_}%s${_CTRL_Z_}%d${_CTRL_Z_}" "$(some_command)" "${?}" 1>&2) 2>&1)
EOF
' "$(((({ ; echo "${?}" 1>&3-; } | tr -d '_CTRL_Z_=$'\cZ'
# SYNTAX:
# catch_posix STDOUT_VARIABLE STDERR_VARIABLE COMMAND
catch_posix() {
{
IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" "";
IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" "";
(IFS=$'\n'"${_CTRL_Z_}" read -r -d "${_CTRL_Z_}" _ERRNO_; return ${_ERRNO_});
} <<EOF
$((printf "${_CTRL_Z_}%s${_CTRL_Z_}%d${_CTRL_Z_}" "$(((({ ; echo "${?}" 1>&3-; } | cut -z -d"${_CTRL_Z_}" -f1 | tr -d '# Wrappers to avoid polluting the current shell's environment with variables
: catch_read returncode FD variable
catch_read()
{
eval "=\"\`cat <&\`\"";
# You can use read instead to skip some fork()s.
# However read stops at the first NUL byte,
# also does no \n removal and needs bash 3 or above:
#IFS='' read -ru -d '' "";
return ;
}
: catch_1 tempfile variable comand args..
catch_1()
{
{
rm -f "";
"${@:3}" 66<&-;
catch_read $? 66 "";
} 2>&1 >"" 66<"";
}
: catch stdout stderr command args..
catch()
{
catch_1 "`tempfile`" "${2:-stderr}" catch_1 "`tempfile`" "${1:-stdout}" "${@:3}";
}
' 1>&4-) 4>&2- 2>&1- | cut -z -d"${_CTRL_Z_}" -f1 | tr -d '#######
# runs "$@" and outputs both stdout and stderr on stdin, both in a prefixed format allowing both std in and out to be separately stored in variables later.
# limitations: Bash does not allow null to be returned from subshells, limiting the usefullness of applying this function to commands with null in the output.
# example:
# var=$(keepBoth ls . notHere)
# echo ls had the exit code "$(extractOne r "$var")"
# echo ls had the stdErr of "$(extractOne e "$var")"
# echo ls had the stdOut of "$(extractOne o "$var")"
keepBoth() {
(
prefix(){
( set -o pipefail
base64 -w 1 - | (
while read c
do echo -E "" "$c"
done
)
)
}
( (
"$@" | prefix o >&3
echo ${PIPESTATUS[0]} | prefix r >&3
) 2>&1 | prefix e >&1
) 3>&1
)
}
extractOne() { # extract
echo "" | grep "^" | cut --delimiter=' ' --fields=2 | base64 --decode -
}
' 1>&4-) 3>&1- | exit "$(cat)") 4>&1-)" "${?}" 1>&2) 2>&1)
EOF
}
' 1>&4-) 4>&2- 2>&1- | tr -d 'GET_STDERR=""
GET_STDOUT=""
get_stderr_stdout() {
GET_STDERR=""
GET_STDOUT=""
unset t_std t_err
eval "$( (eval ) 2> >(t_err=$(cat); typeset -p t_err) > >(t_std=$(cat); typeset -p t_std) )"
GET_STDERR=$t_err
GET_STDOUT=$t_std
}
get_stderr_stdout "command"
echo "$GET_STDERR"
echo "$GET_STDOUT"
' 1>&4-) 3>&1- | exit "$(cat)") 4>&1-)" "${?}" 1>&2) 2>&1)
}
%s##代码##%d##代码##' "$(((({ some_command; echo "${?}" 1>&3-; } | tr -d '##代码##' 1>&4-) 4>&2- 2>&1- | tr -d '##代码##' 1>&4-) 3>&1- | exit "$(cat)") 4>&1-)" "${?}" 1>&2) 2>&1)
%s##代码##%d##代码##' "$(some_command)" "${?}" 1>&2) 2>&1)
%s\n##代码##' "$(some_command; printf '##代码##%d' "${?}" 1>&2)" 1>&2) 2>&1)
%s\n##代码##' "$(some_command; printf '##代码##%d' "${?}" 1>&2)" 1>&2) 2>&1)
'; printf '##代码##'; } 2>&1- 1>&4- | tr -d '##代码##' 1>&4-) 3>&1- | xargs printf '##代码##%s##代码##' 1>&4-) 4>&1-)
'; printf '##代码##'; } 2>&1- 1>&4- | tr -d '##代码##' 1>&4-) 3>&1- | xargs printf '##代码##%s##代码##' 1>&4-) 4>&1-)
%s##代码##' "$((some_command | tr -d '##代码##') 3>&1- 1>&2- 2>&3- | tr -d '##代码##')" 1>&2) 2>&1)
The actual script that will capture stdout
and stderr
: capture.sh
将捕获stdout
和的实际脚本stderr
:capture.sh
##代码##
Output of capture.sh
输出 capture.sh
##代码##
3. How it works
3. 工作原理
The command
命令
##代码##sends the standard output of some_command
to printf '\0%s\0'
, thus creating the string \0${stdout}\n\0
(where \0
is a NUL
byte and \n
is a new line character); the string \0${stdout}\n\0
is then redirected to the standard error, where the standard error of some_command
was already present, thus composing the string ${stderr}\n\0${stdout}\n\0
, which is then redirected back to the standard output.
发送some_command
to的标准输出printf '\0%s\0'
,从而创建字符串\0${stdout}\n\0
(其中\0
是一个NUL
字节,\n
是一个换行符);\0${stdout}\n\0
然后将字符串重定向到标准错误,其中 的标准错误some_command
已经存在,从而组成字符串${stderr}\n\0${stdout}\n\0
,然后将其重定向回标准输出。
Afterwards, the command
之后,命令
##代码##starts reading the string ${stderr}\n\0${stdout}\n\0
up until the first NUL
byte and saves the content into ${CAPTURED_STDERR}
. Then the command
开始读取字符串${stderr}\n\0${stdout}\n\0
直到第一个NUL
字节并将内容保存到${CAPTURED_STDERR}
. 然后命令
keeps reading the same string up to the next NUL
byte and saves the content into ${CAPTURED_STDOUT}
.
继续读取相同的字符串直到下一个NUL
字节并将内容保存到${CAPTURED_STDOUT}
.
4. Making it unbreakable
4. 使其牢不可破
The solution above relies on a NUL
byte for the delimiter between stderr
and stdout
, therefore it will not work if for any reason stderr
contains other NUL
bytes.
上面的解决方案依赖于一个NUL
字节用于之间的分隔符stderr
和stdout
,因此它不会如果由于任何原因的工作stderr
包含其他NUL
字节。
Although that should never happen, it is possible to make the script completely unbreakable by stripping all possible NUL
bytes from stdout
and stderr
before passing both outputs to read
(sanitization) – NUL
bytes would anyway get lost, as it is not possible to store them into shell variables:
尽管这永远不会发生,但可以通过在将两个输出传递给(清理)之前和之前剥离所有可能的NUL
字节来使脚本完全牢不可破——字节无论如何都会丢失,因为不可能将它们存储到 shell 变量中:stdout
stderr
read
NUL
Requires:printf
, read
, tr
需要:printf
, read
,tr
EDIT
编辑
I have removed one further example for propagating the exit status to the current shell, because, as Andyhas pointed out in the comments, it was not as “unbreakable” as it was supposed to be (since it did not use printf
to buffer one of the streams). For the record I paste the problematic code here:
我删除了另一个用于将退出状态传播到当前 shell 的示例,因为正如Andy在评论中指出的那样,它并不像预期的那样“牢不可破”(因为它没有printf
用于缓冲其中一个流)。为了记录,我在这里粘贴有问题的代码:
Preserving the exit status (still unbreakable)
The following variant propagates also the exit status of
##代码##some_command
to the current shell:Requires:
printf
,read
,tr
,xargs
保留退出状态(仍然牢不可破)
以下变体还将 的退出状态传播
##代码##some_command
到当前 shell:需要:
printf
,read
,tr
,xargs
Andyhas then submitted the following “suggested edit” for capturing the exit code:
然后,安迪提交了以下“建议编辑”以捕获退出代码:
Simple and clean solution saving the exit value
We can add to the end of
##代码##stderr
, a third piece of information, anotherNUL
plus theexit
status of the command. It will be outputted afterstderr
but beforestdout
保存退出值的简单而干净的解决方案
我们可以在 末尾添加
##代码##stderr
第三条信息,另外NUL
加上exit
命令的状态。它将在之后stderr
但之前输出stdout
His solution seems to work, but has the minor problem that the exit status should be placed as the last fragment of the string, so that we are able to launch exit "${CAPTURED_EXIT}"
within round brackets and not pollute the global scope, as I had tried to do in the removed example. The other problem is that, as the output of his innermost printf
gets immediately appended to the stderr
of some_command
, we can no more sanitize possible NUL
bytes in stderr
, because among these now there is also ourNUL
delimiter.
他的解决方案似乎有效,但有一个小问题,即退出状态应作为字符串的最后一个片段放置,以便我们能够exit "${CAPTURED_EXIT}"
在圆括号内启动而不污染全局范围,正如我尝试在删除的例子。另一个问题是,由于他最里面的输出printf
会立即附加到stderr
of 中some_command
,我们无法再清理 中可能的NUL
字节stderr
,因为在这些字节中现在也有我们的NUL
分隔符。
After thinking a bit about the ultimate approach, I have come out with a solution that uses printf
to cache bothstdout
and the exit code as two different arguments, so that they never interfere.
思考一些关于最终的办法后,我拿出一个解决方案,使用printf
缓存都stdout
和退出代码为两个不同的参数,所以,他们从来没有干涉。
The first thing I did was outlining a way to communicate the exit status to the third argument of printf
, and this was something very easy to do in its simplest form (i.e. without sanitization).
我做的第一件事是概述一种将退出状态传达给 的第三个参数的方法printf
,这是很容易以最简单的形式(即没有清理)做的事情。
5. Preserving the exit status – the blueprint (without sanitization)
5. 保留退出状态 – 蓝图(未经消毒)
##代码##Requires:exit
, printf
, read
需要:exit
, printf
,read
Things get very messy though when we try to introduce sanitization. Launching tr
for sanitizing the streams does in fact overwrite our previous exit status, so apparently the only solution is to redirect the latter to a separate descriptor before it gets lost, keep it there until tr
does its job twice, and then redirect it back to its place.
但是,当我们尝试引入消毒时,事情会变得非常混乱。tr
为清理流而启动实际上会覆盖我们之前的退出状态,因此显然唯一的解决方案是在后者丢失之前将其重定向到一个单独的描述符,将其保留在那里直到tr
完成它的工作两次,然后将其重定向回它的位置.
After some quite acrobatic redirections between file descriptors, this is what I came out with.
在文件描述符之间进行了一些相当复杂的重定向之后,这就是我得出的结论。
6. Preserving the exit status with sanitization – unbreakable (rewritten)
6. 通过消毒保持退出状态——牢不可破(重写)
The code below is a rewriting of the example that I have removed. It also sanitizes possible NUL
bytes in the streams, so that read
can always work properly.
下面的代码是对我已删除的示例的重写。它还可以清理NUL
流中可能的字节,以便read
始终正常工作。
Requires:exit
, printf
, read
, tr
需要:exit
, printf
, read
,tr
This solution is really robust. The exit code is always kept separated in a different descriptor until it reaches printf
directly as a separate argument.
这个解决方案非常强大。退出代码总是在不同的描述符中保持分离,直到它printf
作为单独的参数直接到达。
7. The ultimate solution – a general purpose function with exit status
7. 终极解决方案——具有退出状态的通用函数
We can also transform the code above to a general purpose function.
我们还可以将上面的代码转换为通用函数。
##代码##Requires:cat
, exit
, printf
, read
, tr
需要:cat
, exit
, printf
, read
,tr
With the catch
function we can launch the following snippet,
使用该catch
函数,我们可以启动以下代码段,
and get the following result:
并得到以下结果:
##代码##8. What happens in the last examples
8. 在最后的例子中发生了什么
Here follows a fast schematization:
下面是一个快速的示意图:
some_command
is launched: we then havesome_command
'sstdout
on the descriptor 1,some_command
'sstderr
on the descriptor 2 andsome_command
's exit code redirected to the descriptor 3stdout
is piped totr
(sanitization)stderr
is swapped withstdout
(using temporarily the descriptor 4) and piped totr
(sanitization)- the exit code (descriptor 3) is swapped with
stderr
(now descriptor 1) and piped toexit $(cat)
stderr
(now descriptor 3) is redirected to the descriptor 1, end expanded as the second argument ofprintf
- the exit code of
exit $(cat)
is captured by the third argument ofprintf
- the output of
printf
is redirected to the descriptor 2, wherestdout
was already present - the concatenation of
stdout
and the output ofprintf
is piped toread
some_command
启动:我们再有some_command
'Sstdout
的描述符1some_command
的stderr
对描述符2和some_command
重定向到请求3的退出码stdout
被输送到tr
(消毒)stderr
与stdout
(临时使用描述符 4)交换并通过管道传输到tr
(消毒)- 退出代码(描述符 3)与
stderr
(现在是描述符 1)交换并通过管道传送到exit $(cat)
stderr
(现在描述符 3)被重定向到描述符 1,结束扩展为第二个参数printf
- 的退出代码
exit $(cat)
由第三个参数捕获printf
- 的输出
printf
被重定向到描述符 2,那里stdout
已经存在 - 的串联
stdout
和 的输出通过printf
管道传输到read
9. The POSIX-compliant version #1 (breakable)
9. 符合 POSIX 的版本 #1(可破解)
Process substitutions(the < <()
syntax) are not POSIX-standard (although they de factoare). In a shell that does not support the < <()
syntax the only way to reach the same result is via the <<EOF … EOF
syntax. Unfortunately this does not allow us to use NUL
bytes as delimiters, because these get automatically stripped out before reaching read
. We must use a different delimiter. The natural choice falls onto the CTRL+Z
character (ASCII character no. 26). Here is a breakableversion (outputs must never contain the CTRL+Z
character, or otherwise they will get mixed).
进程替换(< <()
语法)不是 POSIX 标准的(尽管它们事实上是)。在不支持< <()
语法的shell 中,获得相同结果的唯一方法是通过<<EOF … EOF
语法。不幸的是,这不允许我们使用NUL
字节作为分隔符,因为它们在到达read
. 我们必须使用不同的分隔符。自然选择落在CTRL+Z
字符上(ASCII 字符编号 26)。这是一个可破解的版本(输出不得包含该CTRL+Z
字符,否则它们会混在一起)。
Requires:exit
, printf
, read
需要:exit
, printf
,read
10. The POSIX-compliant version #2 (unbreakable, but not as good as the non-POSIX one)
10. 符合 POSIX 的版本 #2(牢不可破,但不如非 POSIX 版本好)
And here is its unbreakable version, directly in function form (if either stdout
or stderr
contain CTRL+Z
characters, the stream will be truncated, but will never be exchanged with another descriptor).
这是它的牢不可破的版本,直接以函数形式(如果其中一个stdout
或stderr
包含CTRL+Z
字符,则流将被截断,但永远不会与另一个描述符交换)。
Requires:cat
, cut
, exit
, printf
, read
, tr
需要:cat
, cut
, exit
, printf
, read
,tr
回答by Tino
For the benefit of the reader here is a solution using tempfile
s.
为了读者的利益,这里是一个使用tempfile
s的解决方案。
The question was not to use tempfile
s. However this might be due to the unwanted pollution of /tmp/
with tempfile in case the shell dies. In case of kill -9
some trap 'rm "$tmpfile1" "$tmpfile2"' 0
does not fire.
问题是不使用tempfile
s。然而,这可能是由于/tmp/
在 shell 死亡的情况下对 tempfile造成了不必要的污染。万一kill -9
有些trap 'rm "$tmpfile1" "$tmpfile2"' 0
不火。
If you are in a situation where you can use tempfile
, but want to never leave debris behind, here is a recipe.
如果您处于可以使用tempfile
但不想留下碎片的情况下,这里有一个秘诀。
Again it is called catch()
(as my other answer) and has the same calling syntax:
它再次被调用catch()
(作为我的另一个答案)并且具有相同的调用语法:
catch stdout stderr command args..
catch stdout stderr command args..
What it does:
它能做什么:
It creates two
tempfile
s forstdout
andstderr
. However it nearly immediately removes these, such that they are only around for a very short time.catch_1()
catchesstdout
(FD 1) into a variable and movesstderr
tostdout
, such that the next ("left")catch_1
can catch that.Processing in
catch
is done from right to left, so the leftcatch_1
is executed last and catchesstderr
.
它
tempfile
为stdout
and创建了两个sstderr
。然而,它几乎立即删除了这些,因此它们只存在很短的时间。catch_1()
捕获stdout
(FD 1)到一个变量中并移动stderr
到stdout
,这样下一个(“左”)catch_1
可以捕获它。处理 in
catch
从右到左完成,所以 leftcatch_1
最后执行并捕获stderr
。
The worst which can happen is, that some temporary files show up on /tmp/
, but they are always empty in that case. (They are removed before they get filled.). Usually this should not be a problem, as under Linux tmpfs supports roughly 128K files per GB of main memory.
可能发生的最坏情况是,一些临时文件出现在 上/tmp/
,但在这种情况下它们总是空的。(它们在填充之前被移除。)。通常这应该不是问题,因为在 Linux 下 tmpfs 支持每 GB 主内存大约 128K 文件。
The given command can access and alter all local shell variables as well. So you can call a shell function which has sideffects!
This only forks twice for the
tempfile
call.
给定的命令也可以访问和更改所有本地 shell 变量。所以你可以调用一个有副作用的shell函数!
这只会为
tempfile
呼叫分叉两次。
Bugs:
错误:
Missing good error handling in case
tempfile
fails.This does the usual
\n
removal of the shell. See comment incatch_read()
.You cannot use file descriptor
66
to pipe data to your command. If you need that, use another descriptor for the redirection, like42
(note that very old shells only offer FDs up to 9).This cannot handle NUL bytes (
$'\0'
) instdout
andstderr
. (NUL is just ignored. For theread
variant everything behind a NUL is ignored.)
如果
tempfile
失败,则缺少良好的错误处理。这将执行通常
\n
的外壳移除。见评论catch_read()
。您不能使用文件描述符
66
将数据通过管道传输到您的命令。如果需要,请使用另一个描述符进行重定向,例如42
(请注意,非常旧的 shell 仅提供最多 9 个 FD)。这不能处理NUL字节(
$'\0'
)中stdout
和stderr
。(NUL 只是被忽略。对于read
变体,NUL 后面的所有内容都被忽略。)
FYI:
供参考:
- Unix allows us to access deleted files, as long as you keep some reference to them around (such as an open filehandle). This way we can open and then remove them.
- Unix 允许我们访问已删除的文件,只要您保留对它们的一些引用(例如打开的文件句柄)。这样我们就可以打开然后删除它们。
回答by mncl
Did not like the eval, so here is a solution that uses some redirection tricks to capture program output to a variable and then parses that variable to extract the different components. The -w flag sets the chunk size and influences the ordering of std-out/err messages in the intermediate format. 1 gives potentially high resolution at the cost of overhead.
不喜欢 eval,所以这里有一个解决方案,它使用一些重定向技巧将程序输出捕获到变量,然后解析该变量以提取不同的组件。-w 标志设置块大小并影响中间格式中 std-out/err 消息的顺序。1 以开销为代价提供了潜在的高分辨率。
##代码##回答by Eduardo Lucio
What about... =D
怎么样... =D
##代码##回答by Jonathan Leffler
Succinctly, I believe the answer is 'No'. The capturing $( ... )
only captures standard output to the variable; there isn't a way to get the standard error captured into a separate variable. So, what you have is about as neat as it gets.
简而言之,我相信答案是“不”。捕获$( ... )
只捕获变量的标准输出;没有办法将标准错误捕获到单独的变量中。所以,你所拥有的一切都是那么整洁。