web-dev-qa-db-ja.com

bashスクリプト自体からstdoutのコピーをログファイルにリダイレクトします

stdoutをファイルにリダイレクトする方法を知っています。

exec > foo.log
echo test

これにより、foo.logファイルに「テスト」が記録されます。

出力をログファイルにリダイレクトし、stdoutに保持する

つまり、スクリプトの外部から簡単に行うことができます:

script | tee foo.log

しかし、スクリプト内で宣言したい

私は試した

exec | tee foo.log

しかし、それはうまくいきませんでした。

219
Vitaly Kushner
#!/usr/bin/env bash

# Redirect stdout ( > ) into a named pipe ( >() ) running "tee"
exec > >(tee -i logfile.txt)

# Without this, only stdout would be captured - i.e. your
# log file would not contain any error messages.
# SEE (and upvote) the answer by Adam Spiers, which keeps STDERR
# as a separate stream - I did not want to steal from him by simply
# adding his answer to mine.
exec 2>&1

echo "foo"
echo "bar" >&2

これはbashではなくshであることに注意してください。 sh myscript.shでスクリプトを呼び出すと、syntax error near unexpected token '>'の行に沿ってエラーが発生します。

シグナルトラップを使用している場合は、tee -iオプションを使用して、シグナルが発生した場合の出力の中断を回避することができます。 (コメントについてはJamesThomasMoon1979に感謝します。)


パイプに書き込むかターミナルに書き込むかによって出力を変更するツール(たとえば、色と列化された出力を使用するls)は、パイプに出力することを意味する上記の構造を検出します。

色付け/列化を強制するオプションがあります(例:ls -C --color=always)。これにより、ログファイルにもカラーコードが書き込まれ、less読み取り可能になることに注意してください。

279
DevSolar

受け入れられた回答は、STDERRを個別のファイル記述子として保持しません。つまり

./script.sh >/dev/null

barを端末に出力せず、ログファイルにのみ出力します。

./script.sh 2>/dev/null

foobarの両方を端末に出力します。明らかに、これは通常のユーザーが期待する動作ではありません。これは、同じログファイルに追加する2つの別々のteeプロセスを使用して修正できます。

#!/bin/bash

# See (and upvote) the comment by JamesThomasMoon1979 
# explaining the use of the -i option to tee.
exec >  >(tee -ia foo.log)
exec 2> >(tee -ia foo.log >&2)

echo "foo"
echo "bar" >&2

(上記は最初にログファイルを切り捨てないことに注意してください-その動作が必要な場合は、追加する必要があります

>foo.log

スクリプトの先頭へ。)

POSIX.1-2008のtee(1) 仕様では、出力がバッファリングされていない、つまりラインバッファリングされていないことが必要であるため、この場合、STDOUTとSTDERRが同じ結果になる可能性がありますfoo.logの行;ただし、それは端末でも発生する可能性があるため、ログファイルは、正確なミラーではない場合、端末で表示されるcouldを忠実に反映しますそれの。 STDOUT行をSTDERR行からきれいに分離したい場合は、2つのログファイルを使用することを検討してください。各行に日付スタンプのプレフィックスを付けて、後で時系列の再アセンブリを可能にします。

163
Adam Spiers

busybox、macOS bash、および非bashシェルのソリューション

受け入れられた答えは確かにbashの最良の選択です。私はbashにアクセスせずにBusybox環境で作業しており、exec > >(tee log.txt)構文を理解していません。また、exec >$PIPEを適切に実行せず、名前付きパイプと同じ名前の通常のファイルを作成しようとして失敗し、ハングします。

これがbashを持たない他の誰かに役立つことを願っています。

また、名前付きパイプを使用している人にとっては、rm $PIPEを使用しても安全です。これにより、パイプがVFSからリンク解除されますが、それを使用するプロセスは終了するまで参照カウントを維持します。

$ *の使用は必ずしも安全ではないことに注意してください。

#!/bin/sh

if [ "$SELF_LOGGING" != "1" ]
then
    # The parent process will enter this branch and set up logging

    # Create a named piped for logging the child's output
    PIPE=tmp.fifo
    mkfifo $PIPE

    # Launch the child process with stdout redirected to the named pipe
    SELF_LOGGING=1 sh $0 $* >$PIPE &

    # Save PID of child process
    PID=$!

    # Launch tee in a separate process
    tee logfile <$PIPE &

    # Unlink $PIPE because the parent process no longer needs it
    rm $PIPE    

    # Wait for child process, which is running the rest of this script
    wait $PID

    # Return the error code from the child process
    exit $?
fi

# The rest of the script goes here
25
jbarlow

スクリプトファイル内で、次のようにすべてのコマンドをかっこで囲みます。

(
echo start
ls -l
echo end
) | tee foo.log
18
WReach

Syslogにbashスクリプトのログを作成する簡単な方法。スクリプト出力は、/var/log/syslogとstderrの両方で利用可能です。 syslogはタイムスタンプなどの有用なメタデータを追加します。

この行を上部に追加します。

exec &> >(logger -t myscript -s)

または、ログを別のファイルに送信します。

exec &> >(ts |tee -a /tmp/myscript.output >&2 )

これには、moreutilsが必要です(タイムスタンプを追加するtsコマンドの場合)。

13
Tobu

受け入れられた回答を使用すると、スクリプトは非常に早く戻り(「exec>>(tee ...)」の直後)、残りのスクリプトはバックグラウンドで実行されたままになります。私は自分のやり方でその解決策を得ることができなかったので、問題の別の解決策/回避策を見つけました:

# Logging setup
logfile=mylogfile
mkfifo ${logfile}.pipe
tee < ${logfile}.pipe $logfile &
exec &> ${logfile}.pipe
rm ${logfile}.pipe

# Rest of my script

これにより、スクリプトからの出力がプロセスからパイプ経由で「tee」のサブバックグラウンドプロセスに送られ、ディスクおよびスクリプトの元の標準出力にすべてが記録されます。

'exec&>'はstdoutとstderrの両方をリダイレクトすることに注意してください。必要に応じて個別にリダイレクトすることも、stdoutだけが必要な場合は 'exec>'に変更することもできます。

スクリプトの最初でファイルシステムからパイプが削除されても、プロセスが終了するまで機能し続けます。 rm-lineの後のファイル名を使用して参照することはできません。

9
fgunger

Bash 4には coproc コマンドがあり、コマンドへの名前付きパイプを確立し、それを介して通信できるようにします。

1

私はexecに基づいたソリューションのいずれかに満足しているとは言えません。私はteeを直接使用することを好むので、要求されたときにスクリプトがteeでそれ自体を呼び出すようにします。

# my script: 

check_tee_output()
{
    # copy (append) stdout and stderr to log file if TEE is unset or true
    if [[ -z $TEE || "$TEE" == true ]]; then 
        echo '-------------------------------------------' >> log.txt
        echo '***' $(date) $0 $@ >> log.txt
        TEE=false $0 $@ 2>&1 | tee --append log.txt
        exit $?
    fi 
}

check_tee_output $@

rest of my script

これにより、次のことが可能になります。

your_script.sh args           # tee 
TEE=true your_script.sh args  # tee 
TEE=false your_script.sh args # don't tee
export TEE=false
your_script.sh args           # tee

これをカスタマイズできます。代わりにtee = falseをデフォルトに設定し、TEEに代わりにログファイルを保持させるなど。

0
Oliver