How do I get both STDOUT and STDERR to go to the terminal and a log file?
我有一个脚本,将由非技术用户交互运行。脚本将状态更新写入stdout,这样用户就可以确保脚本运行正常。
我希望将stdout和stderr都重定向到终端(这样用户就可以看到脚本是否正常工作,以及是否有问题)。我还希望两个流都重定向到日志文件。
我在网上看到了很多解决方案。有些人不工作,有些人非常复杂。我已经开发了一个可行的解决方案(我将输入它作为答案),但它很笨拙。
完美的解决方案是将一行代码合并到任何将两个流同时发送到终端和日志文件的脚本的开头。
编辑:将stderr重定向到stdout,并通过管道将结果发送到tee,这是可行的,但这取决于用户是否记住重定向和传输输出。我希望日志记录是防错和自动的(这就是为什么我希望能够将解决方案嵌入到脚本本身中)。
使用"tee"重定向到文件和屏幕。根据使用的shell,首先必须使用将stderr重定向到stdout
1 | ./a.out 2>&1 | tee output |
或
1 | ./a.out |& tee output |
在CSH中,有一个名为"script"的内置命令,它将捕获所有进入屏幕的文件。首先输入"script",然后执行想要捕获的操作,然后点击control-d关闭脚本文件。我不知道sh/bash/ksh的等价物。
另外,由于您现在已经指出这些是您自己的可以修改的sh脚本,所以您可以通过用大括号或括号包围整个脚本来进行内部重定向,例如
1 2 3 4 | #!/bin/sh { ... whatever you had in your script before } 2>&1 | tee output.file |
半个十年后…
我相信这是行动计划寻求的"完美解决方案"。
下面是一个可以添加到bash脚本顶部的一行程序:
1 | exec > >(tee -a $HOME/logfile) 2>&1 |
下面是一个小脚本演示它的用法:
1 2 3 4 5 6 7 8 9 | #!/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一起使用。)
从这里改编;据我所知,原作没有在日志文件中捕获stderr。用这里的注释修复。
若要将stderr重定向到stdout,请在命令:
这两个看起来都是这样的:
1 | mycommand 2>&1 | tee mylogfile.log |
编辑:对于嵌入到脚本中,您也可以这样做。所以你的剧本
1 2 3 4 5 | #!/bin/sh whatever1 whatever2 ... whatever3 |
将最终成为
1 2 3 4 5 | #!/bin/sh ( whatever1 whatever2 ... whatever3 ) 2>&1 | tee mylogfile.log |
一年后,这里有一个用于记录任何东西的老bash脚本。例如,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | #!/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:]_ ' - ` } #------------------------------------------------------------------------------- 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 |
我创建了一个名为"runscript.sh"的脚本。此脚本的内容是:
1 | ${APP_HOME}/${1}.sh ${2} ${3} ${4} ${5} ${6} 2>&1 | tee -a ${APP_HOME}/${1}.log |
我这样称呼它:
1 | ./RunScript.sh ScriptToRun Param1 Param2 Param3 ... |
这是可行的,但它要求通过外部脚本运行应用程序的脚本。有点笨拙。
使用tee程序和dup stderr to stdout。
1 | program 2>&1 | tee > logfile |
这是一个技巧,也保留了stdout和stderr之间的区别:
1 | { the_cmd > >(tee stdout.txt ); } 2> >(tee stderr.txt >&2 ) |
下面是一个脚本:
1 2 3 4 5 6 7 | the_cmd() { echo out; 1>&2 echo err; } { the_cmd > >(tee stdout.txt ); } 2> >(tee stderr.txt >&2 ) |
下面是一个会话:
1 2 3 4 5 6 7 8 9 10 11 | $ foo=$(./example.sh) err $ echo $foo out $ cat stdout.txt out $ cat stderr.txt err |
工作原理如下:
支架中的部件将作为一个单元运行。其stdout将直接写入stdout,但其stderr将被发送到正确的部分。
首先,
然后,步骤2中的stderr被发送到
在脚本(man 1脚本)中使用
创建一个包装shellscript(2行),设置script(),然后调用exit。
第1部分:包装
1 2 3 | #!/bin/sh script -c './realscript.sh' exit |
第2部分:realscript.sh
1 2 | #!/bin/sh echo 'Output' |
结果:
1 2 3 4 5 6 7 8 9 10 | ~: 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 ~: |