web-dev-qa-db-ja.com

STDOUTとSTDERRの両方を取得して、ターミナルとログファイルに移動するにはどうすればよいですか?

技術者以外のユーザーがインタラクティブに実行するスクリプトがあります。スクリプトは、ユーザーがスクリプトが正常に実行されていることを確認できるように、ステータス更新をSTDOUTに書き込みます。

STDOUTとSTDERRの両方を端末にリダイレクトする必要があります(これにより、ユーザーはスクリプトが機能していることと、問題が発生したかどうかを確認できます)。また、両方のストリームをログファイルにリダイレクトする必要があります。

ネット上で多くのソリューションを見てきました。動作しないものもあれば、恐ろしく複雑なものもあります。私は実行可能なソリューションを開発しました(これを回答として入力します)が、それは気味が悪いです。

完璧なソリューションは、両方のストリームを端末とログファイルの両方に送信するスクリプトの先頭に組み込むことができる1行のコードです。

編集: STDERRをSTDOUTにリダイレクトし、結果をteeにパイピングしますが、出力をリダイレクトしてパイピングすることを覚えているユーザーに依存します。ロギングを絶対確実で自動化したい(これが、ソリューションをスクリプト自体に埋め込むことができるようにしたい理由です)。

75
JPLemme

「tee」を使用して、ファイルと画面にリダイレクトします。使用するシェルに応じて、最初にstderrをstdoutにリダイレクトする必要があります

./a.out 2>&1 | tee output

または

./a.out |& tee output

Cshには、画面に表示されるすべてをファイルにキャプチャする「スクリプト」と呼ばれる組み込みコマンドがあります。 「script」と入力して開始し、キャプチャしたいことを何でも実行してから、control-Dを押してスクリプトファイルを閉じます。 sh/bash/kshに相当するものがわかりません。

また、これらは変更可能な独自のshスクリプトであることが示されているため、スクリプト全体を中括弧または大括弧で囲むことにより、内部的にリダイレクトを行うことができます。

  #!/bin/sh
  {
    ... whatever you had in your script before
  } 2>&1 | tee output.file
125
Paul Tomblin

半年後に近づく...

これはOPが求める「完璧な解決策」だと思います。

Bashスクリプトの上部に追加できる1つのライナーを次に示します。

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ではnot動作します。)

here ;から適応私の知る限りでは、オリジナルはログファイルでSTDERRをキャッチしませんでした。 here からのメモで修正されました。

13
Jason Sydes

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
flolo

1年後、ここに何かを記録するための古いbashスクリプトがあります。例えば、
teelog make ...は生成されたログ名にログを記録します(ネストされたmakesもログに記録するトリックを参照してください)。

#!/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
1
denis

これはトリックを行い、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

仕組みは次のとおりです。

  1. the_cmdのstdoutはteeに送信され、ファイルに保存されますが、そのブロックのstdout({braces}の部分)にコピーも渡されます。 the_cmdのstderrは、デフォルトでブロックのstderrになります。

  2. ブロックからstdoutをリダイレクトしないので(既にファイルにキャプチャしているため)、デフォルトで端末に送信しますが、ブロックのstderrを別のteeコマンドにリダイレクトします(キャプチャする必要があるため) )。

  3. 最後のteeコマンドは、stdin(the_cmdのstderr)をファイルに書き込みますが、コピーをstdoutに渡します。これにより、ブロックのstdoutと混合されます(これは望ましくありません) 、したがって、>&2で明示的にstderrに送り返します。

これにより、ファイルとコマンドの出力の両方でstderrがstdoutから分離されます。

最初のティーがエラーを書き込むと、それらはstderrファイルとコマンドのstderrの両方に表示されます。秒ティーがエラーを書き込むと、それらは端末のstderrにのみ表示されます。

Teeプログラムとdup stderrを使用してstdoutします。

 program 2>&1 | tee > logfile
1
tvanfosson

「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 ...

これは機能しますが、外部スクリプトを介してアプリケーションのスクリプトを実行する必要があります。それは少し気味が悪いです。

1
JPLemme

スクリプトでscriptコマンドを使用します(man 1スクリプト)

Script()をセットアップしてからexitを呼び出すラッパーシェルスクリプト(2行)を作成します。

パート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
~:
0
gnud