在 Bash 中同时捕获标准输出和标准错误 [重复]
Posted
技术标签:
【中文标题】在 Bash 中同时捕获标准输出和标准错误 [重复]【英文标题】:Capture both stdout and stderr in Bash [duplicate] 【发布时间】:2012-11-28 04:50:00 【问题描述】:我知道这个语法
var=`myscript.sh`
或
var=$(myscript.sh)
将myscript.sh
的结果(stdout
)捕获到var
。如果我想同时捕获两者,我可以将stderr
重定向到stdout
。如何将它们中的每一个保存到单独的变量中?
我的用例是,如果返回码不为零,我想回显stderr
,否则取消。可能还有其他方法可以做到这一点,但这种方法似乎会奏效,如果它真的可能的话。
【问题讨论】:
啊,没有临时文件就无法同时捕获两者,请参阅我的答案,其中显示了如何获取 stderr 并将 stdout 传递到屏幕(在对话框的情况下):***.com/a/13427218/815386 这里是附加信息mywiki.wooledge.org/BashFAQ/002What you cannot do is capture stdout in one variable, and stderr in another, using only FD redirections. You must use a temporary file (or a named pipe) to achieve that one.
您不想使用临时文件有什么具体原因吗?在 bash 编程环境中使用临时文件非常习惯
相关(并且有一个非常简单的解决方案):Bash script - store stderr in variable
@eicto 是的,有办法,阅读here。
【参考方案1】:
有一种非常丑陋的方法可以在没有临时文件的两个单独变量中捕获stderr
和stdout
(如果您喜欢管道),适当地使用process substitution、source
和declare
。我会打电话给你的命令banana
。您可以使用函数模拟这样的命令:
banana()
echo "banana to stdout"
echo >&2 "banana to stderr"
我假设您希望变量bout
中的banana
标准输出和变量berr
中banana
的标准错误。这是实现这一目标的魔法(仅限 Bash≥4):
. <( berr=$( bout=$(banana); 2>&1; declare -p bout >&2); declare -p berr; 2>&1)
那么,这里发生了什么?
让我们从最里面的术语开始:
bout=$(banana)
这只是将banana
的标准输出分配给bout
的标准方式,标准错误会显示在您的终端上。
然后:
bout=$(banana); 2>&1
仍将banana
的stdout分配给bout
,但banana
的stderr通过stdout显示在终端上(感谢重定向2>&1
。
然后:
bout=$(banana); 2>&1; declare -p bout >&2
会像上面那样做,但也会在终端上(通过 stderr)显示 bout
的内容和 declare
内置:这将很快被重用。
然后:
berr=$( bout=$(banana); 2>&1; declare -p bout >&2); declare -p berr
将banana
的stderr分配给berr
,并用declare
显示berr
的内容。
此时,您的终端屏幕上将显示:
declare -- bout="banana to stdout"
declare -- berr="banana to stderr"
用线
declare -- bout="banana to stdout"
通过标准错误显示。
最终重定向:
berr=$( bout=$(banana); 2>&1; declare -p bout >&2); declare -p berr; 2>&1
将通过标准输出显示上一个。
最后,我们使用process substitution 来获取这些行的内容。
您也提到了命令的返回码。将banana
更改为:
banana()
echo "banana to stdout"
echo >&2 "banana to stderr"
return 42
我们还将在变量bret
中拥有banana
的返回码,如下所示:
. <( berr=$( bout=$(banana); bret=$?; 2>&1; declare -p bout bret >&2); declare -p berr; 2>&1)
您也可以通过使用 eval
来实现无需采购和流程替换(它也适用于 Bash
eval "$( berr=$( bout=$(banana); bret=$?; 2>&1; declare -p bout bret >&2); declare -p berr; 2>&1)"
这一切都是安全的,因为我们source
ing 或eval
ing 的唯一内容是从declare -p
获得的,并且总是会被正确转义。
当然,如果您希望以数组形式输出(例如,使用 mapfile
,如果您使用 Bash≥4,否则将 mapfile
替换为 while
–read
循环),适配很简单。
例如:
banana()
printf 'banana to stdout %d\n' 1..10
echo >&2 'banana to stderr'
return 42
. <( berr=$( mapfile -t bout < <(banana); 2>&1; declare -p bout >&2); declare -p berr; 2>&1)
并带有返回码:
. <( berr=$( mapfile -t bout< <(banana; bret=$?; declare -p bret >&3); 3>&2 2>&1; declare -p bout >&2); declare -p berr; 2>&1)
【讨论】:
真棒答案 - 这太强大了! @gniourf_gniourf 我可以使用您的eval
示例并复制相同的banana
输出。接下来,我尝试通过变量传递命令并让它与ls "foo"
一起工作,但ls "foo bar"
出现问题——注意引号中的空格。在后一种情况下,我看到在 berr 中捕获了以下错误:ls: cannot access "foo: No such file or directory ls: cannot access bar": No such file or directory
这使我相信文件路径周围的引用不足以逃避空间。你能想出一个解决方案吗?顺便说一句,设置 IFS=''
不起作用。
@gniourf_gniourf 文章Bash: Preserving Whitespace Using set and eval 让我想知道使用 eval 保留空格是否是根本问题。我不确定我目前对问题的理解是否足够好,无法确定如何解决它。
@JohnMarkMitchell 你在这里使用了反模式! Don't put commands into variables!.
所以你基本上是动态生成源代码的! ingenious!谢谢你!【参考方案2】:
没有临时文件就无法同时捕获两者。
您可以将标准错误捕获到变量并将标准输出传递给用户屏幕(示例来自here):
exec 3>&1 # Save the place that stdout (1) points to.
output=$(command 2>&1 1>&3) # Run command. stderr is captured.
exec 3>&- # Close FD #3.
# Or this alternative, which captures stderr, letting stdout through:
output=$(command 2>&1 1>&3-) ; 3>&1
但是没有办法同时捕获stdout和stderr:
你不能做的是在一个变量中捕获 stdout,在另一个变量中捕获 stderr,只使用 FD 重定向。您必须使用临时文件(或命名管道)来实现该文件。
【讨论】:
谢谢 - 接受这个答案,因为使用文件描述符 3 及更高版本可以避免使用临时文件(即使在技术上,字面上是临时文件)所指的“临时文件”。 谢谢。基于这个答案,我使用 fd 3 在两个脚本之间发送额外信息,并使用 output=$(command 3>&1 1>&4-) ; 4>&1
从调用脚本中捕获它
我反对!有一种方法可以同时捕获标准输出和标准错误。 You can capture stdout into one and stderr into another variable with only FD redirections. No need to use some temporary file or named pipe。唯一的缺点就是有点丑。【参考方案3】:
你可以这样做:
OUT=$(myscript.sh 2> errFile)
ERR=$(<errFile)
现在$OUT
将有你的脚本的标准输出,$ERR
有你的脚本的错误输出。
【讨论】:
对不起,我忘了指定没有临时文件。【参考方案4】:一种简单但不优雅的方法:将 stderr 重定向到一个临时文件,然后将其读回:
TMP=$(mktemp)
var=$(myscript.sh 2> "$TMP")
err=$(cat "$TMP")
rm "$TMP"
【讨论】:
【参考方案5】:虽然我还没有找到一种方法来捕获 stderr 和 stdout 以在 bash 中分隔变量,但我将两者发送到同一个变量...
result=$( grep "JUNK" ./junk.txt; 2>&1 )
...然后我检查退出状态“$?”,并根据 $result 中的数据进行适当的操作。
【讨论】:
【参考方案6】:# NAME
# capture - capture the stdout and stderr output of a command
# SYNOPSIS
# capture <result> <error> <command>
# DESCRIPTION
# This shell function captures the stdout and stderr output of <command> in
# the shell variables <result> and <error>.
# ARGUMENTS
# <result> - the name of the shell variable to capture stdout
# <error> - the name of the shell variable to capture stderr
# <command> - the command to execute
# ENVIRONMENT
# The following variables are mdified in the caller's context:
# - <result>
# - <error>
# RESULT
# Retuns the exit code of <command>.
# SOURCE
capture ()
# Name of shell variable to capture the stdout of command.
result=$1
shift
# Name of shell variable to capture the stderr of command.
error=$1
shift
# Local AWK program to extract the error, the result, and the exit code
# parts of the captured output of command.
local evaloutput='
output [NR] = $0
END \
firstresultline = NR - output [NR - 1] - 1
if (Var == "error") \
for (i = 1; i < firstresultline; ++ i)
printf ("%s\n", output [i])
else if (Var == "result") \
for (i = firstresultline; i < NR - 1; ++ i)
printf ("%s\n", output [i])
else \
printf ("%d", output [NR])
'
# Capture the stderr and stdout output of command, as well as its exit code.
local output="$(
local stdout
stdout="$($*)"
local exitcode=$?
printf "\n%s\n%d\n%d\n" \
"$stdout" "$(echo "$stdout" | wc -l)" "$exitcode"
2>&1)"
# extract the stderr, the stdout, and the exit code parts of the captured
# output of command.
printf -v $error "%s" \
"$(echo "$output" | gawk -v Var="error" "$evaloutput")"
printf -v $result "%s" \
"$(echo "$output" | gawk -v Var="result" "$evaloutput")"
return $(echo "$output" | gawk "$evaloutput")
【讨论】:
这是错误的:赋值result=$(command)
在子shell 中运行:error=$( ... )
中括号内的所有内容都在子shell 中;因此result
将永远不会被看到。你可以自己试试:c() echo >&2 'to stderr'; echo 'to stdout'; ; error=$( result=$(c); 2>&1); echo "result: $result"; echo "error: $error"
。你会看到result
是空的。请参阅我的答案以了解实际有效的方法。以上是关于在 Bash 中同时捕获标准输出和标准错误 [重复]的主要内容,如果未能解决你的问题,请参考以下文章