コマンドを実行すると、標準出力または標準エラー出力によってログがターミナル上に表示されることがあります。
このログを取っておきたい時や誰かに共有したい時に、そのログをコピペするのも1つの手ですが、ファイルにそのまま書き出した方が扱いやすかったり変なコピペミスなどを回避できたりします。
標準出力
標準出力をファイルに吐き出すには>
または>>
(もしくは1>
または1>>
)を使います。>
と>>
の違いはファイル全体の上書きか、追記か、の違いがあります。
この>
はリダイレクトと呼ばれます。
以下はstdout.log
にログを出力しているシェルスクリプトの例です。
#!/usr/bin/env bash
# stdout.log をリセット
: > stdout.log
echo foo >> stdout.log
echo bar >> stdout.log
このシェルスクリプトを実行するとstdout.log
はこうなっているハズです。
cat stdout.log
# foo
# bar
: > stdout.log
部分は、これ無しで何度もこのシェルスクリプトを実行すると、foo\nbar...
が追記され続けてしまうので、前回のログを消すために行ってます。
標準エラー出力
エラーの場合は2>
または2>>
を使ってファイルに出力できます。
以下はstderr.log
にログを出力しているシェルスクリプトの例です。
この中で標準エラー出力を手っ取り早く試す為にecho ... >&2
を使ってます。この>&2
または1>&2
は、標準出力を標準エラー出力としてログを出すという意味です。
#!/usr/bin/env bash
# stderr.log をリセット
: > stderr.log
echo foo >&2 2>> stderr.log
echo bar >&2 2>> stderr.log
このシェルスクリプトを実行するとstdout.log
も先程の標準出力と同じ結果になります。
cat stderr.log
# foo
# bar
両方
1つのコマンドで両方に対処することもできます。
#!/usr/bin/env bash
log() {
if [ $1 -eq 1 ]; then
echo "$2"
else
echo "$2" >&2
fi
}
: >stdout.log >stderr.log
log 1 foo >>stdout.log 2>>stdout.log
log 2 bar >>stderr.log 2>>stderr.log
echo
では分かりづらい為、log
関数を実装してます。これは最初の引数が1
なら第2引数のテキストを標準出力、そうでないなら標準エラー出力してます。
>>stdout.log 2>>stdout.log
部分では、標準出力はstdout.log
標準エラー出力はstderr.log
にというように 1 つのコマンドに対して同時に設定してます。
このシェルスクリプトを実行するとそれらログファイルが作成され、それぞれ別れて追記されてるのが分かります。
cat stdout.log
# foo
cat stderr.log
# bar
リダイレクトを消す
シェルスクリプトの中では恐らくコマンドが 1 つや 2 つではなく、>>stdout.log 2>>stdout.log
という部分を毎回記述するのは面倒だけでなく、タイプミスなど凡ミスも起こしやすいので、回避する為にexec
コマンドを使います。
コマンド実行の前にexec > stdout.log 2> stderr.log
(>>
ではないので注意)とすることで、それ以降のすべての実行ログを、exec
で行ったリダイレクトに渡してくれます。
例えば先程の両方セクションのシェルスクリプトは以下のようにリファクタリングできます。
#!/usr/bin/env bash
log() {
if [ $1 -eq 1 ]; then
echo "$2"
else
echo "$2" >&2
fi
}
exec > stdout.log 2> stderr.log
log 1 foo
log 2 bar
cat stdout.log
# foo
cat stderr.log
# bar
ログファイルに出す領域を切り分けたい
exec
は後で「実行時にターミナルにログ出したい」などのように思っても切り替えができません。
ですが、1 つのシェルスクリプトの中で、「この領域はファイルに出したいけど、この部分のはいらない」のように切り替えたい場合は、その領域を{...}
で囲み、リダイレクトするという手もあります。
以下は上記「リダイレクトを消す」セクションのシェルスクリプトを少し書き直したものです。
#!/usr/bin/env bash
log() {
if [ $1 -eq 1 ]; then
echo "$2"
else
echo "$2" >&2
fi
}
# ファイルにログ出し
{
log 1 foo
log 2 bar
} >stdout.log 2>stderr.log
# ターミナルにログ出し
log 1 foo
log 2 bar
このシェルスクリプトを実行すると{...}
に囲まれた部分はファイルに書き出され、そうじゃない部分は普通にターミナルにログが出力されます。