捕获输出和错误到不同的变量捕获输出和错误到不同的变量(Capture stdout and stde

2019-06-01 01:24发布

是否有可能存储或不同的变量捕获输出和错误,不使用临时文件? 现在我这样做是为了得到在标准输出out和标准错误在err中运行时some_command ,但我想避免临时文件。

error_file=$(mktemp)
out=$(some_command 2>$error_file)
err=$(< error_file)
rm $error_file

Answer 1:

好吧,它得到了一个有点难看,但这里是一个解决方案:

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)。



Answer 2:

乔纳森有答案 。 作为参考,这是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支持。



Answer 3:

这个命令设置标准输出(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|"


Answer 4:

要总结的一切了读者的利益,这里是一个

易可重复使用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它还重定向。 stderrdummystdout ,使得外键锁可以收集到stdout

  • ret=$?; 捕捉退出代码,这是1

  • printf '%q=%q\n' "$1" "$__1" >&2; 然后输出out=2astderrstderr在这里使用,因为目前的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=3astderrstderr被再次使用,因为它已被用于输出其它变量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是从$12a是从stdoutdummyerr是从$23b是从stderrdummy ,并且1是从返回代码dummy

请注意, %q在格式printf需要照顾的报价,使得外壳看到正确的(单)参数,当涉及到eval2a3b是如此简单,它们被逐字拷贝。

现在到外eval "$({ ... } 2>&1 )";

此执行所有的上述哪一个输出的2个变量和exit ,捕获它(为此将2>&1 )并将其解析为使用当前的壳eval

这样,2个变量获取设置和返回代码。

问:它使用eval是邪恶的。 因此,它是安全的呢?

  • 只要printf %q有没有错误,它应该是安全的。 但是,你总是要非常小心,只是想想弹震。

问:错误?

  • 无明显的错误是已知的,除了以下几点:

    • 捕大输出需要很大的内存和CPU,因为一切都进入变量,需要通过shell中背解析。 所以,明智地使用它。
    • 像往常一样, $(echo $'\n\n\n\n') 吞下所有的换行 ,不仅是最后一个。 这是一个POSIX的要求。 如果你需要得到的LF一劫,只需添加一些尾随字符输出事后删除它就像在下面的食谱(看尾随x允许读取软链接指向在其结尾的文件$'\n' ):

       target="$(readlink -e "$file")x" target="${target%x}" 
    • 壳牌变量不能携带字节NUL( $'\0' )。 他们,如果他们碰巧在发生完全忽略stdoutstderr

  • 给定的命令在子子shell中运行。 所以它有没有访问$PPID ,也无法改变shell变量。 你可以catch一个shell功能,即使是建宏,但那些不能改变的shell变量(如内运行一切$( .. )不能做到这一点)。 所以,如果你需要在当前shell中运行的功能,并抓住它的标准错误/标准输出,你需要做的这与通常的方式tempfile秒。 (有办法做到这一点,例如,该中断通常外壳不留杂物后面,但这是复杂的,值得它自己的答案。)

问:Bash的版本?

  • 我认为你需要击4及以上(由于printf %q

问:这看起来还是那么别扭。

  • 对。 这里的另一个答案将展示如何在做ksh更干净。 不过我不习惯ksh ,所以我把它留给别人创建一个类似的易于重用食谱ksh

问:为什么不使用ksh呢?

  • 因为这是一个bash解决方案

问:脚本可以提高

  • 当然,你可以挤出一些字节,创建更小或更多不可理解的解决方案。 只是去了;)

问:有一个错字。 : catch STDOUT STDERR cmd args..应阅读# catch STDOUT STDERR cmd args..

  • 其实这是预期。 :在显示出来bash -x而评论悄悄地吞掉。 所以,你可以看到解析器是,如果你碰巧有在函数定义一个错字。 这是一个古老的调试技巧。 但要注意一点,你可以轻松地创建的参数内一些巧妙sideffects :

编辑:增加了一对夫妇; 使其更容易地创建一个班轮出catch() 并补充部分它是如何工作的。



Answer 5:

从技术上讲,命名管道不是临时文件,这里没有人提到他们。 他们没有存储在文件系统中,你可以为你将它们连接起来,尽快将其删除(所以你永远不会看到他们):

#!/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为你)。



Answer 6:

不喜欢的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 -                                                                                                                                                                                                                                                                                                                                                                                                                           
}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       


Answer 7:

简洁地说,我相信答案是“否”。 捕捉$( ... )只能捕获标准输出到变量; 有没有一种方式来获得捕获到一个单独的变量的标准误差。 所以,你是什么样的整齐,因为它得到。



Answer 8:

怎么样... = 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"


Answer 9:

对于这里的读者的好处是使用的解决方案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代表stdoutstderr 。 然而,它几乎立即删除这些,使得它们只存在了很短的时间。

  • catch_1()捕获stdout (FD 1)代入变量并移动stderrstdout ,以使得下一个(“左”) catch_1可以赶上。

  • 处理catch从右到左进行的,因此左catch_1执行最后和捕捉stderr

其中最坏的情况是,一些临时文件显示在/tmp/ ,但他们总是在这种情况下是空的。 (他们被删除他们得到填补之前)。 通常,这不应该是一个问题,因为Linux下的tmpfs支持每GB的主内存128K大致文件。

  • 给出的命令可以访问和更改所有本地shell变量为好。 所以,你可以调用其中有sideffects壳功能!

  • 这只叉两次对tempfile调用。

错误:

  • 如果缺少良好的错误处理tempfile失败。

  • 这确实平时\n去除外壳。 查看评论catch_read()

  • 不能使用文件描述符66到管数据到命令。 如果你需要使用其它描述符重定向,像42 (注意,很旧壳只提供文件描述符最多9张)。

  • 这不能处理NUL字节( $'\0' )的stdoutstderr 。 (NUL只是忽略。对于read后面的NUL变种都被忽略。)

供参考:

  • UNIX允许我们访问已删除的文件,只要你保持一定的借鉴他们周围(如一个开放的文件句柄)。 这样我们就可以打开,然后删除它们。


Answer 10:

如果该命令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的呼叫。



Answer 11:

这里有一个简单的变化,是不是相当的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

你的想法。



Answer 12:

一个解决办法,这是哈克,但也许比一些本网页上的建议更直观,是来标记输出流,将它们合并和拆分之后基于标签。 例如,我们可以用“标准输出”前缀标签标准输出:

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和/或标准错误是受限制的,你可以拿出不与他们允许的内容相冲突的标签。



Answer 13:

警告:没有(尚未?)工作!

下面似乎是一个可能导致得到它的工作不会产生任何临时文件,也对POSIX只SH; 它需要的base64然而并由于编码/解码可能不是有效的,并使用也“较大”存储器。

  • 即使在简单的情况下,将已经失效,当最后一个标准错误行没有换行符。 这可以在一些情况下与取代的exe被固定在至少“{EXE;回声>&2;}”,即增加一个新行。
  • 主要的问题是在于然而一切似乎情趣。 尝试使用一个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)



文章来源: Capture stdout and stderr into different variables