stdoutをファイルにリダイレクトする方法を知っています。
exec > foo.log
echo test
これにより、foo.logファイルに「テスト」が記録されます。
出力をログファイルにリダイレクトし、stdoutに保持する
つまり、スクリプトの外部から簡単に行うことができます:
script | tee foo.log
しかし、スクリプト内で宣言したい
私は試した
exec | tee foo.log
しかし、それはうまくいきませんでした。
#!/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読み取り可能になることに注意してください。
受け入れられた回答は、STDERRを個別のファイル記述子として保持しません。つまり
./script.sh >/dev/null
bar
を端末に出力せず、ログファイルにのみ出力します。
./script.sh 2>/dev/null
foo
とbar
の両方を端末に出力します。明らかに、これは通常のユーザーが期待する動作ではありません。これは、同じログファイルに追加する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つのログファイルを使用することを検討してください。各行に日付スタンプのプレフィックスを付けて、後で時系列の再アセンブリを可能にします。
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
スクリプトファイル内で、次のようにすべてのコマンドをかっこで囲みます。
(
echo start
ls -l
echo end
) | tee foo.log
Syslogにbashスクリプトのログを作成する簡単な方法。スクリプト出力は、/var/log/syslog
とstderrの両方で利用可能です。 syslogはタイムスタンプなどの有用なメタデータを追加します。
この行を上部に追加します。
exec &> >(logger -t myscript -s)
または、ログを別のファイルに送信します。
exec &> >(ts |tee -a /tmp/myscript.output >&2 )
これには、moreutils
が必要です(タイムスタンプを追加するts
コマンドの場合)。
受け入れられた回答を使用すると、スクリプトは非常に早く戻り(「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の後のファイル名を使用して参照することはできません。
Bash 4には coproc
コマンドがあり、コマンドへの名前付きパイプを確立し、それを介して通信できるようにします。
私は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に代わりにログファイルを保持させるなど。