是否可以在不使用临时文件的情况下在不同变量中存储或捕获stdout和stderr?现在我这样做是为了在运行out
时err
和some_command
中的stderr获取stdout,但我会
喜欢避开临时文件。
error_file=$(mktemp)
out=$(some_command 2>$error_file)
err=$(< error_file)
rm $error_file
答案 0 :(得分:34)
好吧,它有点难看,但这是一个解决方案:
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) )"
其中(echo std; echo err >&2)
需要被实际命令替换。 stdout 的输出逐行保存到数组$t_std
中,省略新行(-t
)和 stderr 到$t_err
。
如果您不喜欢阵列,可以
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) )"
几乎模仿了var=$(cmd)
的行为,除了$?
的值,它将我们带到最后一次修改:
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 )"
此处$?
会保留在$t_ret
使用 GNU bash
测试Debian wheezy,版本 4.2.37(1)-release(i486-pc-linux-gnu)。
答案 1 :(得分:15)
乔纳森有the answer。作为参考,这是ksh93技巧。 (需要非古代版本)。
function out {
echo stdout
echo stderr >&2
}
x=${ { y=$(out); } 2>&1; }
typeset -p x y # Show the values
产生
x=stderr
y=stdout
${ cmds;}
语法只是一个不创建子shell的命令替换。这些命令在当前的shell环境中执行。开头的空格很重要({
是保留字)。
内部命令组的Stderr被重定向到stdout(因此它适用于内部替换)。接下来,将out
的标准输出分配给y
,重定向的stderr由x
捕获,而不会通常丢失y
到命令替换的子shell。
在其他shell中是不可能的,因为捕获输出的所有构造都需要将生成器放入子shell中,在这种情况下,将包括赋值。
更新:现在也受到mksh支持。
答案 2 :(得分:14)
此命令在当前运行的shell中设置stdout(stdval)和stderr(errval)值:
eval "$( execcommand 2> >(setval errval) > >(setval stdval); )"
如果已定义此功能:
function setval { printf -v "$1" "%s" "$(cat)"; declare -p "$1"; }
将execcommand更改为捕获的命令,无论是&#34; ls&#34;,&#34; cp&#34;,&#34; df&#34;等等。
所有这些都是基于我们可以借助函数setval将所有捕获的值转换为文本行的想法,然后使用setval来捕获此结构中的每个值:
execcommand 2> CaptureErr > CaptureOut
将每个捕获值转换为setval调用:
execcommand 2> >(setval errval) > >(setval stdval)
将所有内容包装在执行调用中并回显它:
echo "$( execcommand 2> >(setval errval) > >(setval stdval) )"
您将获得每个setval创建的声明调用:
declare -- stdval="I'm std"
declare -- errval="I'm err"
要执行该代码(并设置变量),请使用eval:
eval "$( execcommand 2> >(setval errval) > >(setval stdval) )"
最后回应set vars:
echo "std out is : |$stdval| std err is : |$errval|
还可以包含返回(退出)值 完整的bash脚本示例如下所示:
#!/bin/bash --
# The only function to declare:
function setval { printf -v "$1" "%s" "$(cat)"; declare -p "$1"; }
# 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|"
答案 3 :(得分:11)
bash
解决方案此版本使用子shell并在没有tempfile
的情况下运行。 (对于没有子shell运行的tempfile
版本,请参阅my other answer。)
: catch STDOUT STDERR cmd args..
catch()
{
eval "$({
__2="$(
{ __1="$("${@:3}")"; } 2>&1;
ret=$?;
printf '%q=%q\n' "$1" "$__1" >&2;
exit $ret
)"
ret="$?";
printf '%s=%q\n' "$2" "$__2" >&2;
printf '( exit %q )' "$ret" >&2;
} 2>&1 )";
}
使用示例:
dummy()
{
echo "$3" >&2
echo "$2" >&1
return "$1"
}
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"
打印
ret=3
stdout=$'\ndiffcult\n data '
stderr=$'\nother\n difficult \n data '
因此可以在不深入思考的情况下使用它。只需将catch VAR1 VAR2
放在任何command args..
前面,就可以了。
有些if cmd args..; then
将成为if catch VAR1 VAR2 cmd args..; then
。真的没什么复杂的。
问:它是如何运作的?
它只是将其他答案中的想法包含在一个函数中,以便可以轻松地重用它。
catch()
基本上使用eval
来设置两个变量。这类似于https://stackoverflow.com/a/18086548
考虑拨打catch out err dummy 1 2a 3b
:
让我们暂时跳过eval "$({
和__2="$(
。我稍后会谈到这个。
__1="$("$("${@:3}")"; } 2>&1;
执行dummy 1 2 3
并将其stdout
存储到__1
中以供日后使用。因此__1
变为2a
。它还会将stderr
的{{1}}重定向到dummy
,以便外部捕获可以收集stdout
stdout
捕获退出代码,即ret=$?;
1
然后将printf '%q=%q\n' "$1" "$__1" >&2;
输出到out=2a
。此处使用stderr
,因为当前stderr
已经接管了stdout
命令stderr
的角色。
dummy
然后将退出代码(exit $ret
)转发到下一个阶段。
现在到外1
:
这会将上述__2="$( ... )"
stdout
调用的stderr
捕获到变量dummy
中。 (我们可以在这里重复使用__2
,但我使用__1
来减少混淆。)因此__2
变为__2
3b
再次捕获(返回)返回代码ret="$?";
(来自1
)
dummy
然后将printf '%s=%q\n' "$2" "$__2" >&2;
输出到err=3a
。再次使用stderr
,因为它已用于输出其他变量stderr
。
out=2a
catch`。
请注意,作为优化,我们可以将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
作为单个printf
&#34; $ __ 2&#34; &#34; $ ret&#34;`以及。
那么到目前为止我们有什么?
我们写了以下内容给stderr:
printf '%s=%q\n( exit %q )
其中out=2a
err=3b
( exit 1 )
来自out
,$1
来自2a
stdout
,dummy
来自err
,{ {1}}来自$2
3b
,而stderr
来自dummy
的返回代码。
请注意1
格式dummy
负责引用,以便shell在%q
时看到正确的(单个)参数。 printf
和eval
非常简单,可以按字面意思复制。
现在到外2a
:
执行以上所有操作,输出2个变量和3b
,捕获它(因此eval "$({ ... } 2>&1 )";
)并使用exit
将其解析为当前shell。
这样就可以设置2个变量和返回代码。
问:它使用的是2>&1
,这是邪恶的。这样安全吗?
eval
没有错误,就应该是安全的。但是你总是要非常小心,只要想想ShellShock。问:错误?
除了以下内容外,没有明显的错误:
像往常一样eval
吞下所有换行,而不仅仅是最后一行。这是POSIX要求。如果你需要保持LF不受伤害,只需在输出中添加一些尾随字符,然后将其删除,如下面的配方(查看尾随printf %q
,它允许读取指向以{结尾的文件的软链接} {1}}):
$(echo $'\n\n\n\n')
Shell变量不能携带字节NUL(x
)。如果它们碰巧发生在$'\n'
或target="$(readlink -e "$file")x"
target="${target%x}"
中,它们就会被忽略。
给定命令在子子shell中运行。因此它无法访问$'\0'
,也无法更改shell变量。你可以stdout
一个shell函数,甚至是内置函数,但是那些不能改变shell变量(因为在stderr
内运行的所有东西都不能这样做)。因此,如果您需要在当前shell中运行函数并捕获它的stderr / stdout,则需要以$PPID
的常规方式执行此操作。 (有很多方法可以做到这一点,中断外壳通常不会留下碎片,但这很复杂,值得它自己的答案。)
问:Bash版本?
catch
)$( .. )
中完成它。但是我不习惯tempfile
,所以我将其留给其他人为printf %q
创建一个类似的易于重复使用的食谱。问:为什么不使用ksh
?
ksh
解决方案问:脚本可以改进
ksh
应为ksh
bash
: catch STDOUT STDERR cmd args..
会在# catch STDOUT STDERR cmd args..
显示,而评论会被默默吞噬。因此,如果碰巧在函数定义中出现拼写错误,您可以看到解析器的位置。这是一个旧的调试技巧。但要注意一点,你可以在:
的参数中轻松创建一些干净的副作用。修改:添加了多个bash -x
,以便更轻松地从:
创建单线程。并添加了它的工作原理。
答案 4 :(得分:7)
从技术上讲,命名管道不是临时文件,这里没有人提到它们。它们不会在文件系统中存储任何内容,您可以在连接它们后立即将其删除(因此您无法看到它们):
#!/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
您可以通过这种方式拥有多个后台进程,并在方便的时候异步收集他们的stdouts和stderrs等。
如果你只需要一个进程,你也可以使用硬编码的fd数字,如3和4,而不是{fdout}/{fderr}
语法(为你找到一个免费的fd)。
答案 5 :(得分:5)
我认为在说“你做不到”之前,人们至少应该亲身尝试一下……
eval
或任何奇特的东西{
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
} < <((printf '\0%s\0' "$(some_command)" 1>&2) 2>&1)
要求:printf
,read
stdout
和stderr
的虚拟脚本:useless.sh
#!/bin/bash
#
# useless.sh
#
echo "This is stderr" 1>&2
echo "This is stdout"
stdout
和stderr
的实际脚本:capture.sh
#!/bin/bash
#
# capture.sh
#
{
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
} < <((printf '\0%s\0' "$(./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
capture.sh
的输出Here is the captured stdout:
This is stdout
And here is the captured stderr:
This is stderr
命令
(printf '\0%s\0' "$(some_command)" 1>&2) 2>&1
将some_command
的标准输出发送到printf '\0%s\0'
,从而创建字符串\0${stdout}\n\0
(其中\0
是NUL
字节,\n
是换行符);然后,将字符串\0${stdout}\n\0
重定向到标准错误,其中已经存在some_command
的标准错误,从而组成了字符串${stderr}\n\0${stdout}\n\0
,然后将其重定向回到标准输出。 / p>
随后,该命令
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
开始读取字符串${stderr}\n\0${stdout}\n\0
直到第一个NUL
字节,然后将内容保存到${CAPTURED_STDERR}
中。然后命令
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
保留读取同一字符串直到下一个NUL
字节,并将内容保存到${CAPTURED_STDOUT}
。
以上解决方案依靠NUL
字节作为stderr
和stdout
之间的定界符,因此,如果出于任何原因stderr
包含其他{{1 }}个字节。
尽管永远不会发生,但可以通过将两个输出都传递到NUL
之前从NUL
和stdout
剥离所有可能的stderr
个字节来使脚本完全不可破坏(消毒)– read
个字节总会丢失,就像it is not possible to store them into shell variables:
NUL
要求:{
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
} < <((printf '\0%s\0' "$((some_command | tr -d '\0') 3>&1- 1>&2- 2>&3- | tr -d '\0')" 1>&2) 2>&1)
,printf
,read
我删除了另一个将退出状态传播到当前shell的示例,因为正如 Andy 在评论中指出的那样,它不像应该的那样“牢不可破” (因为它没有使用tr
来缓冲其中一个流)。为了记录,我将有问题的代码粘贴到这里:
保留退出状态(仍不可中断)
以下变体还将
printf
的退出状态传播到当前shell:some_command
要求:
{ 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 '\0'; printf '\0'; } 2>&1- 1>&4- | tr -d '\0' 1>&4-) 3>&1- | xargs printf '\0%s\0' 1>&4-) 4>&1-)
,printf
,read
,tr
Andy 随后提交了以下“建议的编辑”以捕获退出代码:
简单干净的解决方案,保存退出值
我们可以在
之前输出xargs
的末尾添加第三条信息,另一条stderr
和命令的NUL
状态。将在exit
之后但在stderr
stdout
他的解决方案似乎有效,但是有一个小问题,即退出状态应放置在字符串的最后一个片段中,以便我们能够在圆括号内启动{
IFS= read -r -d '' CAPTURED_STDERR;
IFS= read -r -d '' CAPTURED_EXIT;
IFS= read -r -d '' CAPTURED_STDOUT;
} < <((printf '\0%s\n\0' "$(some_command; printf '\0%d' "${?}" 1>&2)" 1>&2) 2>&1)
而不污染全局范围,就像我在删除的示例中尝试做的那样。另一个问题是,随着他最里面的exit "${CAPTURED_EXIT}"
的输出被立即附加到printf
的{{1}}上,我们无法再清理{{1 }},因为其中还有 our stderr
分隔符。
在仔细考虑了最终方法之后,我提出了一个解决方案,该解决方案使用some_command
来缓存两者 NUL
和退出代码作为两个不同的参数,这样他们就不会干涉。
我所做的第一件事是概述一种将退出状态传达给stderr
的第三个参数的方法,这是最简单的形式(即无需消毒),很容易做到。
NUL
要求:printf
,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 '\0%s\0%d\0' "$(some_command)" "${?}" 1>&2) 2>&1)
来清理流确实会覆盖我们先前的退出状态,因此,显然唯一的解决方案是在后者丢失之前将后者重定向到一个单独的描述符,直到exit
继续工作之前两次,然后将其重定向回原位。
在文件描述符之间进行了一些非常杂技的重定向之后,这就是我的想法。
下面的代码是对我删除的示例的重写。它还清除了流中可能的printf
个字节,以便read
始终可以正常工作。
tr
要求:tr
,NUL
,read
,{
IFS=$'\n' read -r -d '' CAPTURED_STDOUT;
IFS=$'\n' read -r -d '' CAPTURED_STDERR;
(IFS=$'\n' read -r -d '' _ERRNO_; exit ${_ERRNO_});
} < <((printf '\0%s\0%d\0' "$(((({ some_command; echo "${?}" 1>&3-; } | tr -d '\0' 1>&4-) 4>&2- 2>&1- | tr -d '\0' 1>&4-) 3>&1- | exit "$(cat)") 4>&1-)" "${?}" 1>&2) 2>&1)
此解决方案非常强大。退出代码始终保持在不同的描述符中,直到直接作为单独的参数到达exit
。
我们还可以将上面的代码转换为通用函数。
printf
要求:read
,tr
,printf
,# SYNTAX:
# catch STDOUT_VARIABLE STDERR_VARIABLE COMMAND
catch() {
{
IFS=$'\n' read -r -d '' "${1}";
IFS=$'\n' read -r -d '' "${2}";
(IFS=$'\n' read -r -d '' _ERRNO_; return ${_ERRNO_});
} < <((printf '\0%s\0%d\0' "$(((({ ${3}; echo "${?}" 1>&3-; } | tr -d '\0' 1>&4-) 4>&2- 2>&1- | tr -d '\0' 1>&4-) 3>&1- | exit "$(cat)") 4>&1-)" "${?}" 1>&2) 2>&1)
}
,cat
使用exit
函数,我们可以启动以下代码段,
printf
并获得以下结果:
read
这里是快速的模式化:
tr
被启动:然后在描述符1上有catch
的{{1}}在描述符2上有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
的{{1}}在{2 1}}的退出代码重定向到描述符3 The `./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
通过管道传输到some_command
(消毒)some_command
与stdout
交换(临时使用描述符4)并通过管道传递到some_command
(消毒)stderr
(现在为描述符1)交换,并通过管道传递到some_command
stdout
(现为描述符3)重定向到描述符1,并以tr
的第二个自变量扩展stderr
的退出代码由stdout
的第三个参数捕获tr
的输出重定向到描述符2,其中stderr
已经存在exit $(cat)
的串联和stderr
的输出通过管道传递到printf
Process substitutions(exit $(cat)
语法)不是POSIX标准的(尽管它们实际上是 )。在不支持printf
语法的shell中,达到相同结果的唯一方法是通过printf
语法。不幸的是,这不允许我们使用stdout
个字节作为分隔符,因为这些字节在到达stdout
之前会被自动剥离。我们必须使用其他定界符。自然选择落在printf
字符(ASCII字符编号26)上。这是一个易碎版本(输出不得包含read
字符,否则它们将变得混乱)。
< <()
要求:< <()
,<<EOF … EOF
,NUL
这是它的坚不可摧的版本,直接以函数形式显示(如果read
或CTRL+Z
包含CTRL+Z
个字符,则该流将被截断,但永远不会与另一个描述符交换)。
_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
要求:exit
,printf
,read
,stdout
,stderr
,CTRL+Z
答案 6 :(得分:3)
不喜欢eval,所以这里有一个解决方案,它使用一些重定向技巧将程序输出捕获到变量,然后解析该变量以提取不同的组件。 -w标志设置块大小并影响中间格式的std-out / err消息的排序。 1以开销为代价提供了潜在的高分辨率。
#######
# 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 "$1" "$c"
done
)
)
}
( (
"$@" | prefix o >&3
echo ${PIPESTATUS[0]} | prefix r >&3
) 2>&1 | prefix e >&1
) 3>&1
)
}
extractOne() { # extract
echo "$2" | grep "^$1" | cut --delimiter=' ' --fields=2 | base64 --decode -
}
答案 7 :(得分:2)
简洁地说,我相信答案是'不'。捕获$( ... )
仅捕获变量的标准输出;没有办法将标准错误捕获到单独的变量中。所以,你所拥有的就像它得到的一样整洁。
答案 8 :(得分:2)
怎么样...... = D
GET_STDERR=""
GET_STDOUT=""
get_stderr_stdout() {
GET_STDERR=""
GET_STDOUT=""
unset t_std t_err
eval "$( (eval $1) 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"
答案 9 :(得分:2)
为了读者的利益,这里有一个使用tempfile
s。
问题不是使用tempfile
。然而,这可能是由于在shell死亡的情况下使用临时文件对/tmp/
进行了不必要的污染。在kill -9
的情况下,某些trap 'rm "$tmpfile1" "$tmpfile2"' 0
无法触发。
如果您处于使用tempfile
的情况,但希望永远不会留下碎片,这是一个食谱。
同样,它被称为catch()
(作为我的other answer)并具有相同的调用语法:
catch stdout stderr command args..
# Wrappers to avoid polluting the current shell's environment with variables
: catch_read returncode FD variable
catch_read()
{
eval "$3=\"\`cat <&$2\`\"";
# 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$2 -d '' "$3";
return $1;
}
: catch_1 tempfile variable comand args..
catch_1()
{
{
rm -f "$1";
"${@:3}" 66<&-;
catch_read $? 66 "$2";
} 2>&1 >"$1" 66<"$1";
}
: catch stdout stderr command args..
catch()
{
catch_1 "`tempfile`" "${2:-stderr}" catch_1 "`tempfile`" "${1:-stdout}" "${@:3}";
}
它的作用:
它会为tempfile
和stdout
创建两个stderr
。然而,它几乎立即消除了这些,因此它们只是在很短的时间内。
catch_1()
将stdout
(FD 1)捕获到变量中并将stderr
移至stdout
,以便下一个(&#34; left&#34} ;)catch_1
可以捕获它。
catch
中的处理是从右到左完成的,因此左catch_1
最后执行并捕获stderr
。
最糟糕的情况是,/tmp/
上会显示一些临时文件,但在这种情况下它们总是空的。 (它们在被填满之前被移除。)通常这应该不是问题,因为在Linux下,tmpfs每GB主内存支持大约128K文件。
给定的命令也可以访问和更改所有本地shell变量。所以你可以调用一个有副作用的shell函数!
这只会在tempfile
电话中分叉两次。
错误:
tempfile
失败时缺少良好的错误处理。
通常\n
删除shell。请参阅catch_read()
中的评论。
您不能使用文件描述符66
将数据传递给您的命令。如果需要,请使用另一个描述符进行重定向,例如42
(请注意,非常旧的shell仅提供最多9个FD。)
这不能处理$'\0'
和stdout
中的NUL字节(stderr
)。 (NUL被忽略。对于read
变体,NUL后面的所有内容都会被忽略。)
供参考:
答案 10 :(得分:0)
如果命令1)没有状态副作用,2)计算上便宜,最简单的解决方案就是运行两次。我主要将此用于在启动过程中运行的代码,当您还不知道磁盘是否正常工作时。在我的情况下它是一个很小的some_command
所以没有性能命中运行两次,并且该命令没有副作用。
主要好处是它干净且易于阅读。这里的解决方案非常聪明,但我不愿意成为必须维护包含更复杂解决方案的脚本的解决方案。如果您的方案适用于此,我建议使用简单的run-it-two方法,因为它更清洁,更易于维护。
示例:
output=$(getopt -o '' -l test: -- "$@")
errout=$(getopt -o '' -l test: -- "$@" 2>&1 >/dev/null)
if [[ -n "$errout" ]]; then
echo "Option Error: $errout"
fi
同样,这是可以的,因为getopt没有副作用。我知道它的性能安全,因为我的父代码在整个程序中调用的次数少于100次,用户永远不会注意到100次getopt调用和200次getopt调用。
答案 11 :(得分:0)
这是一个更简单的变体,并不是OP想要的,但不同于任何其他选项。您可以通过重新排列文件描述符来获得所需的任何内容。
测试命令:
%> cat xx.sh
#!/bin/bash
echo stdout
>&2 echo stderr
本身就是:
%> ./xx.sh
stdout
stderr
现在,打印stdout,将stderr捕获到变量,&amp;将stdout记录到文件
%> export err=$(./xx.sh 3>&1 1>&2 2>&3 >"out")
stdout
%> cat out
stdout
%> echo
$err
stderr
或记录标准输出&amp;将stderr捕获到变量:
export err=$(./xx.sh 3>&1 1>out 2>&3 )
%> cat out
stdout
%> echo $err
stderr
你明白了。
答案 12 :(得分:0)
一种解决方法是hacky,但可能比本页面上的一些建议更直观,是标记输出流,合并它们,然后根据标记进行拆分。例如,我们可以使用&#34; STDOUT&#34;标记stdout。前缀:
function someCmd {
echo "I am stdout"
echo "I am stderr" 1>&2
}
ALL=$({ someCmd | sed -e 's/^/STDOUT/g'; } 2>&1)
OUT=$(echo "$ALL" | grep "^STDOUT" | sed -e 's/^STDOUT//g')
ERR=$(echo "$ALL" | grep -v "^STDOUT")
```
如果您知道stdout和/或stderr是受限制的表单,您可以提出一个与其允许的内容不冲突的标记。
答案 13 :(得分:0)
警告:不(但是?)正在工作!
以下似乎可能导致它在没有创建任何临时文件的情况下工作,并且仅在POSIX sh上工作;然而,它需要base64,并且由于编码/解码可能效率不高并且也使用&#34;更大&#34;存储器中。
然而,主要的问题是一切似乎都很活泼。尝试使用像:
这样的exeEXE() { cat /usr/share/hunspell/de_DE.dic cat /usr/share/hunspell/en_GB.dic&gt;&amp; 2 }
你会看到,例如base64编码行的部分位于文件的顶部,部分位于末尾,而未解码的stderr位于中间。
好吧,即使下面的想法不能发挥作用(我假设),它也可能成为那些可能错误地相信它可以像这样工作的人的反例。 < / p>
想法(或反例):
#!/bin/sh
exe()
{
echo out1
echo err1 >&2
echo out2
echo out3
echo err2 >&2
echo out4
echo err3 >&2
echo -n err4 >&2
}
r="$( { exe | base64 -w 0 ; } 2>&1 )"
echo RAW
printf '%s' "$r"
echo RAW
o="$( printf '%s' "$r" | tail -n 1 | base64 -d )"
e="$( printf '%s' "$r" | head -n -1 )"
unset r
echo
echo OUT
printf '%s' "$o"
echo OUT
echo
echo ERR
printf '%s' "$e"
echo ERR
给出(使用stderr-newline修复):
$ ./ggg
RAW
err1
err2
err3
err4
b3V0MQpvdXQyCm91dDMKb3V0NAo=RAW
OUT
out1
out2
out3
out4OUT
ERR
err1
err2
err3
err4ERR
(至少在Debian&#dash; bash上)
答案 14 :(得分:0)
这是@madmurphy解决方案的一种变体,适用于任意大的stdout / stderr流,维护出口返回值并处理流中的null(通过将它们转换为换行符)
function buffer_plus_null()
{
local buf
IFS= read -r -d '' buf || :
echo -n "${buf}"
printf '\0'
}
{
IFS= time read -r -d '' CAPTURED_STDOUT;
IFS= time read -r -d '' CAPTURED_STDERR;
(IFS= read -r -d '' CAPTURED_EXIT; exit "${CAPTURED_EXIT}");
} < <((({ { some_command ; echo "${?}" 1>&3; } | tr '\0' '\n' | buffer_plus_null; } 2>&1 1>&4 | tr '\0' '\n' | buffer_plus_null 1>&4 ) 3>&1 | xargs printf '%s\0' 1>&4) 4>&1 )
缺点:
read
命令是该操作中最昂贵的部分。例如:在运行500个进程的计算机上,find /proc
花费20秒(而命令仅为0.5秒)。第一次阅读需要10秒,第二次阅读需要10秒,使总时间增加了一倍。最初的解决方案是使用printf
的参数来缓冲流,但是由于需要使退出代码排在最后,因此一种解决方案是同时缓冲stdout和stderr。我尝试了xargs -0 printf
,但随后您很快就达到了“最大参数长度限制”。因此,我决定一个解决方案是编写一个快速缓冲功能:
read
将流存储在变量中read
将在流结束或接收到null时终止。由于我们已经删除了null,因此它在流关闭时结束,并返回非零值。由于这是预期的行为,因此我们添加|| :
表示“或true”,以便该行始终求值为true(0)echo -n "${buf}"
是内置命令,因此不限于参数长度限制答案 15 :(得分:0)
实时输出并写入文件:
#!/usr/bin/env bash
# File where store the output
log_file=/tmp/out.log
# Empty file
echo > ${log_file}
outToLog() {
# File where write (first parameter)
local f="$1"
# Start file output watcher in background
tail -f "${f}" &
# Capture background process PID
local pid=$!
# Write "stdin" to file
cat /dev/stdin >> "${f}"
# Kill background task
kill -9 ${pid}
}
(
# Long execution script example
echo a
sleep 1
echo b >&2
sleep 1
echo c >&2
sleep 1
echo d
) 2>&1 | outToLog "${log_file}"
# File result
echo '==========='
cat "${log_file}"