我有一个非技术用户以交互方式运行的脚本。该脚本将状态更新写入STDOUT,以便用户可以确保脚本运行正常。
我希望STDOUT和STDERR都重定向到终端(这样用户就可以看到脚本正常工作,看看是否有问题)。我还希望将两个流重定向到日志文件。
我在网上看到了很多解决方案。有些不起作用,有些则非常复杂。我已经开发了一个可行的解决方案(我将作为答案输入),但它很蹩脚。
完美的解决方案是一行代码,可以合并到任何脚本的开头,将脚本发送到终端和日志文件。
编辑:将STDERR重定向到STDOUT并将结果传递给tee工作,但这取决于用户记住重定向和管道输出。我希望日志记录是万无一失的(这就是为什么我希望能够将解决方案嵌入到脚本本身中。)
答案 0 :(得分:117)
使用“tee”重定向到文件和屏幕。根据您使用的shell,首先必须使用
将stderr重定向到stdout./a.out 2>&1 | tee output
或
./a.out |& tee output
在csh中,有一个名为“script”的内置命令可以捕获进入文件的所有内容。您可以通过键入“script”来启动它,然后执行您要捕获的任何操作,然后按control-D关闭脚本文件。我不知道sh / bash / ksh的等价物。
此外,由于您现在已经指出这些是您自己可以修改的sh脚本,您可以通过用大括号或括号括起整个脚本来内部进行重定向,例如
#!/bin/sh
{
... whatever you had in your script before
} 2>&1 | tee output.file
答案 1 :(得分:13)
接近五年后......
我相信这是OP寻求的“完美解决方案”。
以下是您可以添加到Bash脚本顶部的一行内容:
exec > >(tee -a $HOME/logfile) 2>&1
这是一个展示其用途的小脚本:
#!/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
(注意:这仅适用于Bash。不使用/ bin / sh。)
答案 2 :(得分:3)
将stderr重定向到stdout会在您的命令中附加:2>&1
要输出到终端并登录到文件,您应该使用tee
两者一起看起来像这样:
mycommand 2>&1 | tee mylogfile.log
编辑:要嵌入到您的脚本中,您也可以这样做。所以你的脚本
#!/bin/sh
whatever1
whatever2
...
whatever3
最终会成为
#!/bin/sh
( whatever1
whatever2
...
whatever3 ) 2>&1 | tee mylogfile.log
答案 3 :(得分:1)
使用tee程序和dup stderr to stdout。
program 2>&1 | tee > logfile
答案 4 :(得分:1)
我创建了一个名为“RunScript.sh”的脚本。该脚本的内容是:
${APP_HOME}/${1}.sh ${2} ${3} ${4} ${5} ${6} 2>&1 | tee -a ${APP_HOME}/${1}.log
我称之为:
./RunScript.sh ScriptToRun Param1 Param2 Param3 ...
这有效,但它需要通过外部脚本运行应用程序的脚本。这有点笨拙。
答案 5 :(得分:1)
一年后,这是一个用于记录任何内容的旧bash脚本。例如,
teelog make ...
记录到生成的日志名称(并查看用于记录嵌套make
的技巧。)
#!/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`
[[ $1 == "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 "$1" in
-v* | --v* )
echo "$0 version: $Version"
exit 1 ;;
"" | -* )
Help
esac
# scan log= etc --
while [[ $1 == [a-zA-Z_]*=* ]]; do
export "$1"
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
答案 6 :(得分:1)
这可以解决问题,并保留stdout和stderr之间的区别:
{ the_cmd > >(tee stdout.txt ); } 2> >(tee stderr.txt >&2 )
这是一个脚本:
the_cmd()
{
echo out;
1>&2 echo err;
}
{ the_cmd > >(tee stdout.txt ); } 2> >(tee stderr.txt >&2 )
这是一个会话:
$ foo=$(./example.sh)
err
$ echo $foo
out
$ cat stdout.txt
out
$ cat stderr.txt
err
这是它的工作方式:
the_cmd
的 stdout发送到tee
,后者将其保存到文件,但同时将副本传递到其块的stdout({大括号中的部分)。 the_cmd
的stderr默认情况下进入块的stderr。
我们不会将stdout从块中重定向(因为我们已经将其捕获到文件中),因此默认情况下它会将其输出到终端,但是我们确实将块的stderr重定向到另一个tee命令(因为我们仍然需要捕获它。)
最后的tee
命令将其stdin(即the_cmd
的stderr)写入文件,但还将一个副本传递到stdout,它将其与块的stdout混合(我们不需要),因此我们使用>&2
将其显式发送回stderr。
这在文件和命令输出中都使stderr与stdout分开。
如果第一个tee记录了任何错误,则它们将同时显示在stderr文件和命令的stderr中;如果第二个tee记录了任何错误,则它们将仅显示在终端的stderr中。
答案 7 :(得分:1)
编辑: 我发现我出轨了,最终回答了一个与被问到的问题不同的问题。真正问题的答案在保罗·汤姆林的答案的底部。 (如果出于某种原因要增强该解决方案以分别重定向stdout和stderr,则可以使用我在此处描述的技术。)
我一直想要一个保留stdout和stderr之间区别的答案。 不幸的是,到目前为止给出的所有答案都保留了这种区别。 我容易在种族中使用:正如我在评论中指出的那样,它们有可能使程序看到输入不完整的风险。
我想我终于找到了保留该区别的答案, 也不容易比赛,也不是很烦躁。
第一个构建块:交换标准输出和标准错误:
my_command 3>&1 1>&2 2>&3-
第二个构建块:如果我们只想过滤(例如tee)stderr, 我们可以通过交换stdout&stderr,过滤,然后交换回去来实现:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
现在剩下的事情很简单:我们可以在开头添加一个stdout过滤器:
{ { my_command | stdout_filter;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
或末尾:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filter
为使自己确信以上两个命令均有效,我使用了以下命令:
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;}'
输出为:
...(1 second pause)...
teed stdout: to stdout
...(another 1 second pause)...
teed stderr: to stderr
和我的提示一样,在“ teed stderr: to stderr
”之后立即返回。
关于zsh的脚注:
以上解决方案可在bash中运行(我不确定,也许还有其他一些shell),但在zsh中不起作用。它在zsh中失败的原因有两个:
2>&3-
;必须重写
为2>&3 3>&-
因此,例如,我的第二种解决方案必须针对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中使用,但是非常冗长)。
另一方面,您可以利用zsh神秘的内置隐式teeing来获得zsh的更短解决方案,而后者根本不会运行tee:
my_command >&1 >stdout.txt 2>&2 2>stderr.txt
(我不会从文档中猜出我发现>&1
和2>&2
是触发zsh隐式发球的事物;我发现这是通过反复试验得出的。)< / p>
答案 8 :(得分:0)
在脚本中使用script
命令(man 1脚本)
创建一个包装器shellcript(2行),用于设置script(),然后调用exit。
第1部分:wrap.sh
#!/bin/sh
script -c './realscript.sh'
exit
第2部分:realscript.sh
#!/bin/sh
echo 'Output'
结果:
~: 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
~:
答案 9 :(得分:0)
这是一个通过重定向为 bash 工作的解决方案,结合了“kvantour、MatrixManAtYrService”和“Jason Sydes”的解决方案:
#!/bin/bash
exec 1> >(tee x.log) 2> >(tee x.err >&2)
echo "test for log"
echo "test for err" 1>&2
将上面的脚本另存为 x.sh。运行./x.sh后,x.log只包含stdout,而x.err只包含stderr。
答案 10 :(得分:0)
这个问题似乎还没有优雅地解决。 每次我搜索“如何同时输出到 stdout 和 stderr”时,谷歌都会将我引导到这篇文章。
今天,我终于找到了一个简单有效的方法来解决几乎所有这些需求。
本质思想是可以同时打印到多个输出的tee命令,以及linux特有的/proc/self/fd/{1,2,...}来表示stdout、stderr...>
tee /proc/self/fd/2
tee /proc/self/fd/2 file
希望对您有所帮助。