stdoutのCOPYをbashスクリプト自体からログファイルにリダイレクトする


235

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

exec > foo.log
echo test

これにより、「test」がfoo.logファイルに書き込まれます。

次に、出力をログファイルリダイレクトし、標準出力に保持します

つまり、スクリプトの外部から簡単に実行できます。

script | tee foo.log

スクリプト自体の中で宣言したい

私は試した

exec | tee foo.log

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


3
質問の表現が不十分です。あなたは「幹部> foo.log」を起動すると、スクリプトのstdoutがあるファイルfoo.log。foo.log 行くとstdoutになるので、出力をfoo.logとttyに送ることを望んでいると思います。
ウィリアムパーセル2010

私がしたいことは、「exec」で。「exec | tee foo.log」のように、これは完璧です。残念ながら、exec呼び出しでパイプリダイレクトを使用することはできません
Vitaly Kushner

回答:


297
#!/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。これにより、ログファイルにもカラーコードが書き込まれ、読みにくくなります。


5
ほとんどのシステムのT型はバッファリングされているため、スクリプトが終了するまで出力が到着しない場合があります。また、このティーは子プロセスではなくサブシェルで実行されているため、待機を使用して出力を呼び出しプロセスに同期することはできません。あなたが欲しいのは、bogomips.org

14
@Barry:POSIXtee、出力をバッファリングしないように指定しています。それがほとんどのシステムでバッファリングする場合、それはほとんどのシステムで壊れています。それteeは私の解決策ではなく、実装の問題です。
DevSolar 2012

3
@Sebastian:exec非常に強力ですが、非常に複雑です。現在の標準出力を別のファイル記述子に「バックアップ」し、後で回復することができます。Googleの「bash execチュートリアル」には、高度な機能がたくさんあります。
DevSolar 2012

2
@AdamSpiers:私もバリーが何であったのかわかりません。バッシュのがexecされ、文書化、新しいプロセスを開始しないように>(tee ...)、標準的な名前付きパイプ/プロセス置換である、と&?...もちろんのリダイレクトでは、バックグラウンド化とは何の関係もありません:-)
DevSolar

11
に渡す-iことをお勧めしteeます。そうしないと、シグナル割り込み(トラップ)によってメインスクリプトのstdoutが中断されます。たとえば、aがtrap 'echo foo' EXITあり、次にを押したctrl+c場合、「foo」は表示されません。だから私はへの答えを変更しますexec &> >(tee -ia file)
JamesThomasMoon1979

173

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

./script.sh >/dev/null

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

./script.sh 2>/dev/null

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

#!/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。ログファイルが何の忠実な反映となりますので、しかし、それはまた、端末上で起こりうる可能性があり、それを正確にミラーされていない場合、端末上で見られること。STDOUT行をSTDERR行から完全に分離したい場合は、2つのログファイルを使用することを検討してください。ログファイルを後で再構成できるように、各行に日付スタンププレフィックスを付けることができます。


何らかの理由で、私の場合、スクリプトがc-program system()呼び出しから実行されると、メインスクリプトが終了した後でも、2つのT字型サブプロセスが存在し続けます。したがって、私はこのようなトラップを追加する必要がありました:exec > >(tee -a $LOG) trap "kill -9 $! 2>/dev/null" EXIT exec 2> >(tee -a $LOG >&2) trap "kill -9 $! 2>/dev/null" EXIT
alveko

15
に渡す-iことをお勧めしteeます。そうしないと、シグナル割り込み(トラップ)によってスクリプトのstdoutが中断されます。たとえばtrap 'echo foo' EXIT、次にを押した場合ctrl+c、「foo」は表示されません。だから私はへの答えを変更しますexec > >(tee -ia foo.log)
JamesThomasMoon1979

これに基づいて、「ソース化可能な」スクリプトをいくつか作成しました。以下のようなスクリプトでそれらを使用することはできます. log. log foo.logsam.nipl.net/sh/log sam.nipl.net/sh/log-a
サム・ワトキンス

1
この方法の問題は、メッセージSTDOUTが最初にバッチとして表示され、次にメッセージが表示されるSTDERRことです。それらは通常期待されるようにインターリーブされません。
CMCDragonkai 2016

28

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

これは、Macで動作するこれまでに見た唯一のソリューションです
Mike Baglio Jr.

19

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

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


ええ、私はそれを考えましたが、これは現在のシェルの標準出力のリダイレクトではなく、そのようなチートです。実際にサブシェルを実行し、通常のパイパーリダイレクトを実行しています。作品は思った。私はこれと「tail -f foo.log&」ソリューションで分割されています。表面がより良いかどうかを確認するために少し待機します。おそらく解決しない場合、;)
Vitaly Kushner

8
{}は、現在のシェル環境でリストを実行します。()サブシェル環境でリストを実行します。

くそー。ありがとうございました。ここで受け入れられた回答は私にとってはうまくいきませんでした。スクリプトをWindowsシステムのMingWで実行するようにスケジュールしようとしました。私は、実装されていないプロセスの置換について不満を述べたと思います。次の変更後、この回答は正常に機能し、stderrとstdoutの両方をキャプチャしました。tee foo.log +)2>&1 | tee foo.log
ジョンカーター

14

bashスクリプトをsyslogに記録する簡単な方法。スクリプトの出力は/var/log/syslog、stderrとstderrの両方で利用できます。syslogは、タイムスタンプを含む有用なメタデータを追加します。

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

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

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

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

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


10

受け入れられた回答を使用すると、スクリプトは例外的に早く戻り( 'exec>>(tee ...)'の直後)、残りのスクリプトをバックグラウンドで実行したままにしました。その解決策をうまく機能させることができなかったので、問題の別の解決策/回避策を見つけました:

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

# Rest of my script

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

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

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


David Zの2番目のアイデアと同様の答え。そのコメントを見てください。+1 ;-)
olibre 2013

うまくいきます。の$logfile部分がわかりませんtee < ${logfile}.pipe $logfile &。具体的には、これを変更して、完全に拡張されたコマンドログ行(からset -x)をファイルにキャプチャし、に変更してstdoutの先頭に「+」を付けずに行のみを(tee | grep -v '^+.*$') < ${logfile}.pipe $logfile &表示し、に関するエラーメッセージを受け取りました$logfileteeラインをもう少し詳しく説明できますか?
Chris Johnson

私はこれをテストしましたが、この回答はSTDERRを保持しないようです(STDERRはSTDOUTと統合されています)。そのため、エラー検出またはその他のリダイレクトでストリームを分離することに依存している場合は、Adamの回答を確認する必要があります。
HeroCC


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にログファイルを保持させたりすることができます。


-1

これらはどちらも完璧な解決策ではありませんが、次のことを試してみてください。

exec >foo.log
tail -f foo.log &
# rest of your script

または

PIPE=tmp.fifo
mkfifo $PIPE
exec >$PIPE
tee foo.log <$PIPE &
# rest of your script
rm $PIPE

2番目のスクリプトは、スクリプトに問題が発生した場合にパイプファイルをそのままにしておきます。これは、問題である場合とそうでない場合があります(つまりrm、後で親シェルで問題が発生する可能性があります)。


1
tailは、実行中のプロセスを2番目のスクリプトで残します。ティーがブロックするか、&で実行する必要があります。この場合、1番目のプロセスと同じようにプロセスを残します。
Vitaly Kushner、2010

@Vitaly:おっと、背景を忘れたtee-編集しました。先ほど述べたように、どちらも完璧な解決策ではありませんが、バックグラウンドプロセスは親シェルが終了すると強制終了されるので、リソースが永久に消費されることを心配する必要はありません。
デビッドZ

1
いいね:これらは魅力的に見えますが、tail -fの出力もfoo.logに出力されます。execの前にtail -fを実行することでこれを修正できますが、親が終了した後もテールは実行されたままです。あなたは、おそらく0トラップで、明示的にそれを殺すために必要がある
ウィリアム・Pursell

うん。スクリプトがバックグラウンド化されている場合、プロセスはすべて終了しません。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.