Bash脚本 - 将stderr存储在一个变量中

66

我正在编写一个备份数据库的脚本。我有以下代码行:

mysqldump --user=$dbuser --password=$dbpswd  \
   --host=$host $mysqldb | gzip > $filename

我想将stderr赋值给一个变量,这样如果出了问题,它会发送一封电子邮件给我,让我知道发生了什么。我已经找到了将stderr重定向到stdout的解决方案,但是我不能这样做,因为stdout已经被发送(通过gzip)到文件中。如何将stderr单独存储在变量$result中?

4个回答

103

尝试将标准错误重定向到标准输出,并使用$()捕获它。换句话说:

VAR=$((your-command-including-redirect) 2>&1)

由于您的命令将标准输出重定向到其他地方,它不应该影响标准错误输出。也许有更简洁的编写方法,但这样应该可以工作。

编辑:

这确实有效。我已经测试过了:

#!/bin/bash                                                                                                                                                                         
BLAH=$((
(
echo out >&1
echo err >&2
) 1>log
) 2>&1)

echo "BLAH=$BLAH"

将打印出 BLAH=err 并且文件 log 包含 out


@AdamCrume 我该如何更改这个脚本,以便在日志文件中同时记录 stderr 和 stdout(out \n err)? - koby meir
@kobymeir:最简单的方法是执行类似于 foo > out.log 2> err.log 的命令,然后使用 cat out.log err.log > combined.log 将输出和错误信息合并到一个文件中。为了避免覆盖当前目录中的文件,您需要使用 mktemp 在 /tmp 中创建 out.log 和 err.log 文件,并在完成后将它们删除。 - Adam Crume
@AdamCrume 但是使用这种技术,我将无法获得stderr和stdout以它们出现的正确顺序。如果我有: echo err1 >&2 echo out >&1 echo err2 >&2我希望日志输出为: err1 out err2并且错误变量将是: err1 err2 - koby meir
@AdamCrume 好的,我的意思表达得不够清楚。一方面我想要它们交替进行,另一方面我想将所有的stderr捕获到$error变量中。 - koby meir
@kobymeir:我非常确定仅使用Bash无法完成此操作。当您重定向流时,它只能进入一个位置(尽管Bash手册令人困惑地称其为“复制”),因此stderr无法进入日志文件并被$error变量捕获。如果您尝试类似于error=$((foo >> log) |& tee -a log)的操作,则stdout和stderr将无法正确交错。我建议您开一个新问题。 - Adam Crume
显示剩余2条评论

21

对于Bash中的任何通用命令,您可以像这样执行:

{ error=$(command 2>&1 1>&$out); } {out}>&1

正常情况下会有常规输出,任何标准错误都会被捕获到 $error 变量中(在使用时请将其引用为 "$error" 以保留换行符)。要将标准输出捕获到文件中,只需在末尾添加重定向即可,例如:

{ error=$(ls /etc/passwd /etc/bad 2>&1 1>&$out); } {out}>&1 >output

简单解释一下,从外往里看:

  • 为整个代码块创建一个名为 $out 的文件描述符,并将其复制为标准输出(stdout)。
  • 将整个命令的标准输出(stdout)捕获到 $error 中(但请参见下文)。
  • 该命令本身将标准错误(stderr)重定向到标准输出(stdout)(这将被上一步捕获),然后将标准输出(stdout)重定向回代码块外的原始标准输出(stdout),因此只有标准错误(stderr)被捕获。

2
在bash v3.2中无法工作:“unexpected token `{out}'”。这个语法需要Bash 4吗? - Stephen M. Harris
它在bash 4.2和4.1中运行良好,我甚至尝试了shopts:compat32、compat31,也在那里正常工作,也许这是3.2中的一个bug,因为当bash处于兼容模式时它可以工作,除非兼容性仅包括不会因为4.2而破坏的项目,而不是4.2的一部分...(我认为现在大多数人都使用4.2对吧?)无论如何,这是一个旧帖子,但只是为了澄清,它确实可以工作,并且正是我需要的,可以将错误分配给变量,同时在屏幕上显示普通文本... :) - osirisgothra
一个优秀而优雅的解决方案。只有一件事需要补充:之后需要关闭文件描述符:exec {out}>&- - user5429469

6

您可以在将stdout重定向到另一个文件编号(例如3)之前保存其引用,然后将stderr重定向到该引用:

result=$(mysqldump --user=$dbuser --password=$dbpswd  \
   --host=$host $mysqldb 3>&1 2>&3 | gzip > $filename)

所以3>&1将文件号3重定向到标准输出(请注意,这是在使用管道之前)。然后2>&3将stderr重定向到文件号3,现在它与stdout相同。最后,通过将stdout馈送到管道中来重定向stdout,但这不会影响文件号2和3(请注意,从gzip重定向stdout与mysqldump命令的输出无关)。
编辑:更新了命令,将stderr从mysqldump命令重定向而不是gzip,我在第一次回答时太快了。

0

dd同时输出标准输出和标准错误:

$ dd if=/dev/zero count=50 > /dev/null 
50+0 records in
50+0 records out

这两个流是独立的,可以分别重定向:

$ dd if=/dev/zero count=50 2> countfile | wc -c
25600
$ cat countfile 
50+0 records in
50+0 records out
$ mail -s "countfile for you" thornate < countfile

如果你真的需要一个变量:

$ variable=`cat countfile`

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接