bash 如何让 STDOUT 和 STDERR 都转到终端和日志文件?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/363223/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I get both STDOUT and STDERR to go to the terminal and a log file?
提问by JPLemme
I have a script which will be run interactively by non-technical users. The script writes status updates to STDOUT so that the user can be sure that the script is running OK.
我有一个将由非技术用户交互运行的脚本。脚本将状态更新写入 STDOUT,以便用户可以确保脚本运行正常。
I want both STDOUT and STDERR redirected to the terminal (so that the user can see that the script is working as well as see if there was a problem). I also want both streams redirected to a log file.
我希望 STDOUT 和 STDERR 都重定向到终端(以便用户可以看到脚本正在运行以及查看是否有问题)。我还希望两个流都重定向到日志文件。
I've seen a bunch of solutions on the net. Some don't work and others are horribly complicated. I've developed a workable solution (which I'll enter as an answer), but it's kludgy.
我在网上看到了一堆解决方案。有些不起作用,有些则非常复杂。我已经开发了一个可行的解决方案(我将其作为答案输入),但它很笨拙。
The perfect solution would be a single line of code that could be incorporated into the beginning of any script that sends both streams to both the terminal and a log file.
完美的解决方案是一行代码,可以将其合并到任何将流发送到终端和日志文件的任何脚本的开头。
EDIT:Redirecting STDERR to STDOUT and piping the result to tee works, but it depends on the users remembering to redirect and pipe the output. I want the logging to be fool-proof and automatic (which is why I'd like to be able to embed the solution into the script itself.)
编辑:将 STDERR 重定向到 STDOUT 并将结果通过管道传输到 tee 工作,但这取决于用户记得重定向和管道输出。我希望日志记录是万无一失的和自动的(这就是为什么我希望能够将解决方案嵌入到脚本本身中。)
回答by Paul Tomblin
Use "tee" to redirect to a file and the screen. Depending on the shell you use, you first have to redirect stderr to stdout using
使用“tee”重定向到文件和屏幕。根据您使用的 shell,您首先必须使用以下命令将 stderr 重定向到 stdout
./a.out 2>&1 | tee output
or
或者
./a.out |& tee output
In csh, there is a built-in command called "script" that will capture everything that goes to the screen to a file. You start it by typing "script", then doing whatever it is you want to capture, then hit control-D to close the script file. I don't know of an equivalent for sh/bash/ksh.
在 csh 中,有一个名为“script”的内置命令,可以将进入屏幕的所有内容捕获到文件中。您通过键入“脚本”开始它,然后执行您想要捕获的任何操作,然后按 control-D 关闭脚本文件。我不知道 sh/bash/ksh 的等价物。
Also, since you have indicated that these are your own sh scripts that you can modify, you can do the redirection internally by surrounding the whole script with braces or brackets, like
此外,由于您已指出这些是您自己的 sh 脚本,您可以对其进行修改,因此您可以通过用大括号或方括号包围整个脚本来在内部进行重定向,例如
#!/bin/sh
{
... whatever you had in your script before
} 2>&1 | tee output.file
回答by Jason Sydes
Approaching half a decade later...
接近五年后......
I believe this is the "perfect solution" sought by the OP.
我相信这是 OP 寻求的“完美解决方案”。
Here's a one liner you can add to the top of your Bash script:
这是您可以添加到 Bash 脚本顶部的单行代码:
exec > >(tee -a $HOME/logfile) 2>&1
Here's a small script demonstrating its use:
这是一个演示其用法的小脚本:
#!/usr/bin/env bash
exec > >(tee -a $HOME/logfile) 2>&1
# Test redirection of STDOUT
echo test_stdout
# Test redirection of STDERR
ls test_stderr___this_file_does_not_exist
(Note: This only works with Bash. It will notwork with /bin/sh.)
(注意:这只适用于 Bash。它不适用于 /bin/sh。)
Adapted from here; the original did not, from what I can tell, catch STDERR in the logfile. Fixed with a note from here.
回答by MatrixManAtYrService
The Pattern
模式
the_cmd 1> >(tee stdout.txt ) 2> >(tee stderr.txt >&2 )
This redirects both stdout and stderr separately, and it sends separatecopies of stdout and stderr to the caller (which might be your terminal).
这会分别重定向 stdout 和 stderr,并将stdout 和 stderr 的单独副本发送给调用者(可能是您的终端)。
In zsh, it will not proceed to the next statement until the
tee
s have finished.In bash, you may find that the final few lines of output appear afterwhatever statement comes next.
在 zsh 中,在
tee
s 完成之前,它不会继续执行下一条语句。在 bash 中,您可能会发现最后几行输出出现在接下来出现的任何语句之后。
In either case, the right bits go to the right places.
在任何一种情况下,正确的位都到正确的地方。
Explanation
解释
Here's a script (stored in ./example):
这是一个脚本(存储在 ./example 中):
#! /usr/bin/env bash
the_cmd()
{
echo out;
1>&2 echo err;
}
the_cmd 1> >(tee stdout.txt ) 2> >(tee stderr.txt >&2 )
Here's a session:
这是一个会话:
$ foo=$(./example)
err
$ echo $foo
out
$ cat stdout.txt
out
$ cat stderr.txt
err
Here's how it works:
这是它的工作原理:
- Both
tee
processes are started, their stdins are assigned to file descriptors. Because they're enclosed in process substitutions, the paths to those file descriptors are substituted in the calling command, so now it looks something like this:
- 两个
tee
进程都启动了,它们的标准输入被分配给文件描述符。因为它们包含在进程替换中,这些文件描述符的路径在调用命令中被替换,所以现在看起来像这样:
the_cmd 1> /proc/self/fd/13 2> /proc/self/fd/14
the_cmd 1> /proc/self/fd/13 2> /proc/self/fd/14
the_cmd
runs, writing stdout to the first file descriptor, and stderr to the second one.In the bash case, once
the_cmd
finishes, the following statement happens immediately (if your terminal is the caller, then you will see your prompt appear).In the zsh case, once
the_cmd
finishes, the shell waits for both of thetee
processes to finish before moving on. More on this here.The first
tee
process, which is reading fromthe_cmd
's stdout, writes a copy of that stdout back to the caller because that's whattee
does. Its outputs are not redirected, so they make it back to the caller unchangedThe second
tee
process has it'sstdout
redirected to the caller'sstderr
(which is good, because it's stdin is reading fromthe_cmd
's stderr). So when it writes to its stdout, those bits go to the caller's stderr.
the_cmd
运行,将 stdout 写入第一个文件描述符,将 stderr 写入第二个。在 bash 情况下,一旦
the_cmd
完成,以下语句立即发生(如果您的终端是调用者,那么您将看到提示出现)。在 zsh 的情况下,一旦
the_cmd
完成,shell 会等待两个tee
进程都完成后再继续。更多关于这里。第一个
tee
进程从the_cmd
的标准输出中读取,将标准输出的副本写回给调用者,因为这就是tee
它的作用。它的输出不会被重定向,所以它们会原封不动地返回给调用者第二个
tee
进程将它stdout
重定向到调用者的stderr
(这很好,因为它的 stdin 正在从the_cmd
的 stderr读取)。因此,当它写入其标准输出时,这些位将进入调用者的标准错误。
This keeps stderr separate from stdout both in the files and in the command's output.
这使文件和命令输出中的 stderr 与 stdout 分开。
If the first tee writes any errors, they'll show up in both the stderr file and in the command's stderr, if the second tee writes any errors, they'll only show up only in the terminal's stderr.
如果第一个 tee 写入任何错误,它们将同时显示在 stderr 文件和命令的 stderr 中,如果第二个 tee 写入任何错误,它们将仅显示在终端的 stderr 中。
回答by flolo
the to redirect stderr to stdout append this at your command: 2>&1
For outputting to terminal and logging into file you should use tee
将 stderr 重定向到 stdout 在您的命令中附加此:2>&1
对于输出到终端并登录到文件,您应该使用tee
Both together would look like this:
两者放在一起看起来像这样:
mycommand 2>&1 | tee mylogfile.log
EDIT: For embedding into your script you would do the same. So your script
编辑:为了嵌入到你的脚本中,你会做同样的事情。所以你的脚本
#!/bin/sh
whatever1
whatever2
...
whatever3
would end up as
最终会变成
#!/bin/sh
( whatever1
whatever2
...
whatever3 ) 2>&1 | tee mylogfile.log
回答by Don Hatch
EDIT: I see I got derailed and ended up answering a different question from the one asked. The answer to the real question is at the bottom of Paul Tomblin's answer. (If you want to enhance that solution to redirect stdout and stderr separately for some reason, you could use the technique I describe here.)
编辑:我看到我出轨了,最后回答了一个与提出的问题不同的问题。真正问题的答案在 Paul Tomblin 答案的底部。(如果您想增强该解决方案以出于某种原因分别重定向 stdout 和 stderr,您可以使用我在此处描述的技术。)
I've been wanting an answer that preserves the distinction between stdout and stderr. Unfortunately all of the answers given so far that preserve that distinction are race-prone: they risk programs seeing incomplete input, as I pointed out in comments.
我一直想要一个保留标准输出和标准错误之间区别的答案。不幸的是,到目前为止,所有保留这种区别的答案都容易出现竞争:正如我在评论中指出的那样,它们冒着程序看到不完整输入的风险。
I think I finally found an answer that preserves the distinction, is not race prone, and isn't terribly fiddly either.
我想我终于找到了一个保留这种区别的答案,既不偏向于种族,也不十分繁琐。
First building block: to swap stdout and stderr:
第一个构建块:交换 stdout 和 stderr:
my_command 3>&1 1>&2 2>&3-
Second building block: if we wanted to filter (e.g. tee) only stderr, we could accomplish that by swapping stdout&stderr, filtering, and then swapping back:
第二个构建块:如果我们只想过滤(例如 tee)stderr,我们可以通过交换 stdout&stderr、过滤然后交换回来来实现:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
Now the rest is easy: we can add a stdout filter, either at the beginning:
现在剩下的就很简单了:我们可以在开头添加一个标准输出过滤器:
{ { my_command | stdout_filter;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
or at the end:
或最后:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filter
To convince myself that both of the above commands work, I used the following:
为了让自己相信上述两个命令都有效,我使用了以下命令:
alias my_command='{ echo "to stdout"; echo "to stderr" >&2;}'
alias stdout_filter='{ sleep 1; sed -u "s/^/teed stdout: /" | tee stdout.txt;}'
alias stderr_filter='{ sleep 2; sed -u "s/^/teed stderr: /" | tee stderr.txt;}'
Output is:
输出是:
...(1 second pause)...
teed stdout: to stdout
...(another 1 second pause)...
teed stderr: to stderr
and my prompt comes back immediately after the "teed stderr: to stderr
", as expected.
teed stderr: to stderr
正如预期的那样,我的提示在“ ”之后立即返回。
Footnote about zsh:
关于 zsh 的脚注:
The above solution works in bash (and maybe some other shells, I'm not sure), but it doesn't work in zsh. There are two reasons it fails in zsh:
上述解决方案适用于 bash(也许还有其他一些 shell,我不确定),但它不适用于 zsh。在 zsh 中失败有两个原因:
- the syntax
2>&3-
isn't understood by zsh; that has to be rewritten as2>&3 3>&-
- in zsh (unlike other shells), if you redirect a file descriptor that's already open, in some cases (I don't completely understand how it decides) it does a built-in tee-like behavior instead. To avoid this, you have to close each fd prior to redirecting it.
2>&3-
zsh 不理解语法;必须改写为2>&3 3>&-
- 在 zsh 中(与其他 shell 不同),如果您重定向已经打开的文件描述符,在某些情况下(我不完全理解它是如何决定的),它会执行内置的类似 tee 的行为。为避免这种情况,您必须在重定向之前关闭每个 fd。
So, for example, my second solution has to be rewritten for zsh as {my_command 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stderr_filter;} 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stdout_filter
(which works in bash too, but is awfully verbose).
因此,例如,我的第二个解决方案必须为 zsh 重写为{my_command 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stderr_filter;} 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stdout_filter
(它也适用于 bash,但非常冗长)。
On the other hand, you can take advantage of zsh's mysterious built-in implicit teeing to get a much shorter solution for zsh, which doesn't run tee at all:
另一方面,您可以利用 zsh 神秘的内置隐式 tee 为 zsh 获得更短的解决方案,它根本不运行 tee:
my_command >&1 >stdout.txt 2>&2 2>stderr.txt
(I wouldn't have guessed from the docs I found that the >&1
and 2>&2
are the thing that trigger zsh's implicit teeing; I found that out by trial-and-error.)
(我不会从我发现的文档中猜到>&1
和2>&2
是触发 zsh 隐式开球的东西;我通过反复试验发现了这一点。)
回答by gnud
Use the script
command in your script (man 1 script)
script
在脚本中使用命令(man 1 脚本)
Create a wrapper shellscript (2 lines) that sets up script() and then calls exit.
创建一个包装器 shellscript(2 行)来设置 script(),然后调用 exit。
Part 1: wrap.sh
第 1 部分:wrap.sh
#!/bin/sh
script -c './realscript.sh'
exit
Part 2: realscript.sh
第 2 部分:realscript.sh
#!/bin/sh
echo 'Output'
Result:
结果:
~: sh wrap.sh
Script started, file is typescript
Output
Script done, file is typescript
~: cat typescript
Script started on fr. 12. des. 2008 kl. 18.07 +0100
Output
Script done on fr. 12. des. 2008 kl. 18.07 +0100
~:
回答by denis
A year later, here's an old bash script for logging anything. For example,teelog make ...
logs to a generated log name (and see the trick for logging nested make
s too.)
一年后,这是一个用于记录任何内容的旧 bash 脚本。例如,teelog make ...
记录到生成的日志名称(也请参阅记录嵌套make
s的技巧。)
#!/bin/bash
me=teelog
Version="2008-10-9 oct denis-bz"
Help() {
cat <<!
$me anycommand args ...
logs the output of "anycommand ..." as well as displaying it on the screen,
by running
anycommand args ... 2>&1 | tee `day`-command-args.log
That is, stdout and stderr go to both the screen, and to a log file.
(The Unix "tee" command is named after "T" pipe fittings, 1 in -> 2 out;
see http://en.wikipedia.org/wiki/Tee_(command) ).
The default log file name is made up from "command" and all the "args":
$me cmd -opt dir/file logs to `day`-cmd--opt-file.log .
To log to xx.log instead, either export log=xx.log or
$me log=xx.log cmd ...
If "logdir" is set, logs are put in that directory, which must exist.
An old xx.log is moved to /tmp/$USER-xx.log .
The log file has a header like
# from: command args ...
# run: date pwd etc.
to show what was run; see "From" in this file.
Called as "Log" (ln -s $me Log), Log anycommand ... logs to a file:
command args ... > `day`-command-args.log
and tees stderr to both the log file and the terminal -- bash only.
Some commands that prompt for input from the console, such as a password,
don't prompt if they "| tee"; you can only type ahead, carefully.
To log all "make" s, including nested ones like
cd dir1; $(MAKE)
cd dir2; $(MAKE)
...
export MAKE="$me make"
!
# See also: output logging in screen(1).
exit 1
}
#-------------------------------------------------------------------------------
# bzutil.sh denisbz may2008 --
day() { # 30mar, 3mar
/bin/date +%e%h | tr '[A-Z]' '[a-z]' | tr -d ' '
}
edate() { # 19 May 2008 15:56
echo `/bin/date "+%e %h %Y %H:%M"`
}
From() { # header # from: $* # run: date pwd ...
case `uname` in Darwin )
mac=" mac `sw_vers -productVersion`"
esac
cut -c -200 <<!
${comment-#} from: $@
${comment-#} run: `edate` in $PWD `uname -n` $mac `arch`
!
# mac $PWD is pwd -L not -P real
}
# log name: day-args*.log, change this if you like --
logfilename() {
log=`day`
[[ == "sudo" ]] && shift
for arg
do
log="$log-${arg##*/}" # basename
(( ${#log} >= 100 )) && break # max len 100
done
# no blanks etc in logfilename please, tr them to "-"
echo $logdir/` echo "$log".log | tr -C '.:+=[:alnum:]_\n' - `
}
#-------------------------------------------------------------------------------
case "" in
-v* | --v* )
echo " program 2>&1 | tee > logfile
version: $Version"
exit 1 ;;
"" | -* )
Help
esac
# scan log= etc --
while [[ == [a-zA-Z_]*=* ]]; do
export ""
shift
done
: ${logdir=.}
[[ -w $logdir ]] || {
echo >&2 "error: $me: can't write in logdir $logdir"
exit 1
}
: ${log=` logfilename "$@" `}
[[ -f $log ]] &&
/bin/mv "$log" "/tmp/$USER-${log##*/}"
case ${0##*/} in # basename
log | Log ) # both to log, stderr to caller's stderr too --
{
From "$@"
"$@"
} > $log 2> >(tee /dev/stderr) # bash only
# see http://wooledge.org:8000/BashFAQ 47, stderr to a pipe
;;
* )
#-------------------------------------------------------------------------------
{
From "$@" # header: from ... date pwd etc.
"$@" 2>&1 # run the cmd with stderr and stdout both to the log
} | tee $log
# mac tee buffers stdout ?
esac
回答by tvanfosson
Use the tee program and dup stderr to stdout.
使用 tee 程序并将 stderr 复制到 stdout。
${APP_HOME}/.sh 2>&1 | tee -a ${APP_HOME}/.log
回答by JPLemme
I created a script called "RunScript.sh". The contents of this script is:
我创建了一个名为“RunScript.sh”的脚本。这个脚本的内容是:
./RunScript.sh ScriptToRun Param1 Param2 Param3 ...
I call it like this:
我这样称呼它:
##代码##This works, but it requires the application's scripts to be run via an external script. It's a bit kludgy.
这有效,但它需要通过外部脚本运行应用程序的脚本。它有点笨拙。