是否有可能存储或不同的变量捕获输出和错误,不使用临时文件? 现在我这样做是为了得到在标准输出out
和标准错误在err
中运行时some_command
,但我想避免临时文件。
error_file=$(mktemp)
out=$(some_command 2>$error_file)
err=$(< error_file)
rm $error_file
是否有可能存储或不同的变量捕获输出和错误,不使用临时文件? 现在我这样做是为了得到在标准输出out
和标准错误在err
中运行时some_command
,但我想避免临时文件。
error_file=$(mktemp)
out=$(some_command 2>$error_file)
err=$(< error_file)
rm $error_file
好吧,它得到了一个有点难看,但这里是一个解决方案:
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)
需要通过实际的命令来代替。 标准输出的输出被保存到数组$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
测试在Debian喘鸣使用GNU bash
,版本4.2.37(1)-release(1486-PC-Linux的GNU)。
乔纳森有答案 。 作为参考,这是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环境执行。 在开始的空间是很重要的( {
是一个保留字)。
内部命令组的标准错误被重定向到stdout(使得它适用于内取代)。 接着,标准输出out
分配给y
,和被重定向的标准错误被捕获的x
,无需通常的损失y
到一个命令替换的亚层。
这是不可能在其他壳,因为所有的构建物捕获输出需要把制作成一个子shell,在这种情况下,将包括分配。
更新:现在还mksh支持。
这个命令设置标准输出(stdval)和stderr(errval)在当前行驶壳值:
eval "$( execcommand 2> >(setval errval) > >(setval stdval); )"
提供此功能已被定义为:
function setval { printf -v "$1" "%s" "$(cat)"; declare -p "$1"; }
的execCommand改变到捕获命令,无论是“LS”,“CP”,“DF”,等等。
这一切是基于我们都可以捕捉值转换与功能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) )"
最后呼应集瓦尔:
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|"
要总结的一切了读者的利益,这里是一个
bash
解决方案 该版本能够使用子shell并没有运行tempfile
秒。 (对于tempfile
,其运行没有子shell版本,请参阅我的其他答案 。)
: 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 cmd args..; then
将成为if catch VAR1 VAR2 cmd args..; then
if catch VAR1 VAR2 cmd args..; then
。 真的没有什么复杂的。
问:它是如何工作的?
它只是包装从其他答案的想法在这里变成一个功能,使得它可以很容易地resused。
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
的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
。
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 '( 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`。
请注意,作为一种优化,我们可以这样写那些2 printf
作为一个单一的一个类似printf '%s=%q\n( exit %q )
“__ $ 2” “$ RET”'为好。
那么,我们有这么远吗?
我们有以下写入stderr:
out=2a
err=3b
( exit 1 )
其中out
是从$1
, 2a
是从stdout
的dummy
, err
是从$2
, 3b
是从stderr
的dummy
,并且1
是从返回代码dummy
。
请注意, %q
在格式printf
需要照顾的报价,使得外壳看到正确的(单)参数,当涉及到eval
。 2a
和3b
是如此简单,它们被逐字拷贝。
现在到外eval "$({ ... } 2>&1 )";
:
此执行所有的上述哪一个输出的2个变量和exit
,捕获它(为此将2>&1
)并将其解析为使用当前的壳eval
。
这样,2个变量获取设置和返回代码。
问:它使用eval
是邪恶的。 因此,它是安全的呢?
printf %q
有没有错误,它应该是安全的。 但是,你总是要非常小心,只是想想弹震。 问:错误?
无明显的错误是已知的,除了以下几点:
像往常一样, $(echo $'\n\n\n\n')
吞下所有的换行 ,不仅是最后一个。 这是一个POSIX的要求。 如果你需要得到的LF一劫,只需添加一些尾随字符输出事后删除它就像在下面的食谱(看尾随x
允许读取软链接指向在其结尾的文件$'\n'
):
target="$(readlink -e "$file")x" target="${target%x}"
壳牌变量不能携带字节NUL( $'\0'
)。 他们,如果他们碰巧在发生完全忽略stdout
或stderr
。
给定的命令在子子shell中运行。 所以它有没有访问$PPID
,也无法改变shell变量。 你可以catch
一个shell功能,即使是建宏,但那些不能改变的shell变量(如内运行一切$( .. )
不能做到这一点)。 所以,如果你需要在当前shell中运行的功能,并抓住它的标准错误/标准输出,你需要做的这与通常的方式tempfile
秒。 (有办法做到这一点,例如,该中断通常外壳不留杂物后面,但这是复杂的,值得它自己的答案。)
问:Bash的版本?
printf %q
) 问:这看起来还是那么别扭。
ksh
更干净。 不过我不习惯ksh
,所以我把它留给别人创建一个类似的易于重用食谱ksh
。 问:为什么不使用ksh
呢?
bash
解决方案问:脚本可以提高
问:有一个错字。 : catch STDOUT STDERR cmd args..
应阅读# catch STDOUT STDERR cmd args..
:
在显示出来bash -x
而评论悄悄地吞掉。 所以,你可以看到解析器是,如果你碰巧有在函数定义一个错字。 这是一个古老的调试技巧。 但要注意一点,你可以轻松地创建的参数内一些巧妙sideffects :
。 编辑:增加了一对夫妇;
使其更容易地创建一个班轮出catch()
并补充部分它是如何工作的。
从技术上讲,命名管道不是临时文件,这里没有人提到他们。 他们没有存储在文件系统中,你可以为你将它们连接起来,尽快将其删除(所以你永远不会看到他们):
#!/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为你)。
不喜欢的eval,所以这里是一个使用一些技巧重定向捕捉程序输出到一个变量,然后解析该变量来提取不同组件的解决方案。 -w标志设定块大小并影响STD-出/ 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 -
}
简洁地说,我相信答案是“否”。 捕捉$( ... )
只能捕获标准输出到变量; 有没有一种方式来获得捕获到一个单独的变量的标准误差。 所以,你是什么样的整齐,因为它得到。
怎么样... = 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"
对于这里的读者的好处是使用的解决方案tempfile
秒。
问题是不使用tempfile
秒。 然而,这可能是由于不必要的污染/tmp/
在案件的外壳模具临时文件。 在的情况下, kill -9
一些trap 'rm "$tmpfile1" "$tmpfile2"' 0
不火。
如果你是在一个情况下,你可以使用tempfile
,但要永远离开杂物后面 ,这里是一个良方。
同样它被称为catch()
作为我其他的答案 ),并具有相同的调用语法:
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
S代表stdout
和stderr
。 然而,它几乎立即删除这些,使得它们只存在了很短的时间。
catch_1()
捕获stdout
(FD 1)代入变量并移动stderr
到stdout
,以使得下一个(“左”) catch_1
可以赶上。
处理catch
从右到左进行的,因此左catch_1
执行最后和捕捉stderr
。
其中最坏的情况是,一些临时文件显示在/tmp/
,但他们总是在这种情况下是空的。 (他们被删除他们得到填补之前)。 通常,这不应该是一个问题,因为Linux下的tmpfs支持每GB的主内存128K大致文件。
给出的命令可以访问和更改所有本地shell变量为好。 所以,你可以调用其中有sideffects壳功能!
这只叉两次对tempfile
调用。
错误:
如果缺少良好的错误处理tempfile
失败。
这确实平时\n
去除外壳。 查看评论catch_read()
不能使用文件描述符66
到管数据到命令。 如果你需要使用其它描述符重定向,像42
(注意,很旧壳只提供文件描述符最多9张)。
这不能处理NUL字节( $'\0'
)的stdout
和stderr
。 (NUL只是忽略。对于read
后面的NUL变种都被忽略。)
供参考:
如果该命令1)无状态的副作用和2)在计算上是便宜的,最简单的解决办法是只运行它的两倍。 我主要使用本作,当你还不知道如果磁盘将要工作的引导过程中运行的代码。 在我的情况下,它是一个很小的some_command
所以有运行两次没有性能影响,以及该命令没有副作用。
主要的好处是,这是干净的,易于阅读。 这里的解决方案是很聪明,但我不想是具有维持含有更复杂的解决方案脚本的一个。 我建议你简单的运行它,两次的方法,如果您的方案有这样的作品,因为它是更清洁,更容易维护。
例:
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调用VS 200个getopt的呼叫。
这里有一个简单的变化,是不是相当的OP想要的东西,但不同于任何其他选项。 你可以得到任何你想要通过重新排列文件描述符。
测试指令:
%> cat xx.sh
#!/bin/bash
echo stdout
>&2 echo stderr
其本身的作用:
%> ./xx.sh
stdout
stderr
现在,打印标准输出,捕捉错误输出到一个变量,及日志标准输出到文件
%> export err=$(./xx.sh 3>&1 1>&2 2>&3 >"out")
stdout
%> cat out
stdout
%> echo
$err
stderr
或登录标准输出和捕获错误输出到一个变量:
export err=$(./xx.sh 3>&1 1>out 2>&3 )
%> cat out
stdout
%> echo $err
stderr
你的想法。
一个解决办法,这是哈克,但也许比一些本网页上的建议更直观,是来标记输出流,将它们合并和拆分之后基于标签。 例如,我们可以用“标准输出”前缀标签标准输出:
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和/或标准错误是受限制的,你可以拿出不与他们允许的内容相冲突的标签。
警告:没有(尚未?)工作!
下面似乎是一个可能导致得到它的工作不会产生任何临时文件,也对POSIX只SH; 它需要的base64然而并由于编码/解码可能不是有效的,并使用也“较大”存储器。
主要的问题是在于然而一切似乎情趣。 尝试使用一个exe文件,如:
exe文件(){猫/usr/share/hunspell/de_DE.dic猫/usr/share/hunspell/en_GB.dic>&2}
你会看到编码行中的Base64的是如零件是在文件的顶部,在端部,而在中间的未解码标准错误的东西。
好吧,就算下面的想法不能进行工作(我认为),它可以作为一个反例的人谁可能会错误地认为可能是犯了这样的工作。
想法(或反为例):
#!/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
给(与标准错误换行修正):
$ ./ggg
RAW
err1
err2
err3
err4
b3V0MQpvdXQyCm91dDMKb3V0NAo=RAW
OUT
out1
out2
out3
out4OUT
ERR
err1
err2
err3
err4ERR
(至少在Debian的破折号和bash)