| やりたいこと | 書き方 |
|---|---|
| 画面とファイルの両方に出す | cmd | tee out.log |
| stderr も含める | cmd 2>&1 | tee out.log |
| 追記する | cmd | tee -a out.log |
| 複数ファイルに保存する | cmd | tee a.log b.log |
| 終了ステータスを保つ | set -o pipefail または ${PIPESTATUS[0]} |
| 色を残しつつログは素のテキストにする | cmd --color=always 2>&1 | tee >(sed ... > out.log) |
| root 権限のファイルに書く | echo ... | sudo tee -a file > /dev/null |
| Python の出力を即時表示する | python -u script.py 2>&1 | tee out.log |
tee コマンドとは
定義 1 tee コマンド
teeは標準入力から受け取った内容を,標準出力とファイルの両方に書き出すコマンド- ファイルへの保存と標準出力への出力を同時に行える
- 複数のファイルへの出力も可能
コマンド1 | tee ファイル | コマンド2
コマンド | tee ファイル1 ファイル2 ファイル3 ...| 短いオプション | 長いオプション | 意味 |
|---|---|---|
-a |
--append |
既存ファイルを上書きせず追記する(リダイレクトの >> に相当) |
-i |
--ignore-interrupts |
割り込みシグナル(SIGINT)を無視する |
「画面で進捗を見ながら,同じ内容をログとして残す」という,データ分析や開発で頻出する要求を1つのコマンドで満たせます.
リダイレクトとの違い
| 書き方 | 画面 | ファイル |
|---|---|---|
cmd > out.log |
出ない | 出る |
cmd | tee out.log |
出る | 出る |
cmd | tee -a out.log |
出る | 追記される |
基本の使い方
例 1 (リダイレクトとパイプを同時に行う) コマンド > ファイル と コマンド | コマンド を同時に行いたい場合は,間に tee を挟みます.
find /bin /usr/bin -type f | tee cmdlist | lessfind の結果を cmdlist に保存しつつ,less で閲覧しています.
例 2 (複数のファイルに保存する)
ls | tee a.lst b.lst c.lsttee は標準出力にも書き出すので,画面に出したくない場合は出力先の1つをリダイレクトにします.
ls | tee a.lst b.lst > c.lst例 3 (stderr も一緒に残す) パイプが渡すのは stdout だけです.エラーメッセージや進捗表示は stderr に出ることが多いので,ログを取るときは 2>&1 でまとめてから tee に渡します.
cargo test --locked 2>&1 | tee test.logcargo はビルドの進捗を stderr に,テスト結果を stdout に出すため,2>&1 がないとビルドログがファイルに残りません.
開発での使いどころ
終了ステータスを保持する
パイプにすると,終了コードは最後のコマンドである tee のもの(ほぼ常に 0)になります. テストが失敗してもスクリプトや CI が成功扱いになってしまうので,次のどちらかで対処します.
set -o pipefail
cargo test --locked 2>&1 | tee test.logcargo test --locked 2>&1 | tee test.log
exit "${PIPESTATUS[0]}"PIPESTATUSは bash の配列- zsh では小文字の
pipestatusで,添字が1始まりになります(${pipestatus[1]}).
画面の色を残す
パイプ先は TTY ではないため,多くのコマンドは色を自動で無効にします. cargo の場合,cargo 本体とテストハーネスの両方に指定します.
cargo test --locked --color=always -- --color=always 2>&1 | tee test.logただしこの方法ではログにも ANSI エスケープが入ります. ログをプレーンテキストにしたい場合は,プロセス置換を使ってファイルに書く側だけで除去します.
cargo test --locked --color=always -- --color=always 2>&1 \
| tee >(sed -E 's/\x1b\[[0-9;]*[mK]//g' > test.log)他のツールでも考え方は同じで,色を強制するオプションが異なるだけです.
| ツール | 色を強制する指定 |
|---|---|
cargo |
--color=always |
pytest |
--color=yes |
git |
-c color.ui=always |
grep / ls |
--color=always |
root 権限が必要なファイルに書き込む
sudo echo ... > /etc/... は,リダイレクトを処理するのが sudo 前の自分のシェルなので権限エラーになります. 書き込みを tee に任せ,tee を sudo で実行します.
echo "127.0.0.1 mlflow.local" | sudo tee -a /etc/hosts > /dev/null末尾の > /dev/null は画面へのエコーを捨てるためのものです.-a を忘れるとファイルが上書きされる点に注意してください.
データ分析での使いどころ
例 4 (学習・バッチ処理のログを残す) 長時間かかる学習やバッチ処理では,進捗を画面で追いながらログを残します.
python -u train.py --config config.yml 2>&1 | tee "logs/train_$(date +%Y%m%d_%H%M%S).log"Python は出力先が TTY でないと stdout をブロックバッファリングするため,そのままでは画面への表示がまとまって遅れて出ます. -u(または環境変数 PYTHONUNBUFFERED=1)でバッファリングを無効にします.
Python 以外のコマンドでは stdbuf が使えます.
stdbuf -oL ./long_job 2>&1 | tee job.log例 5 (同じログに追記していく) ハイパーパラメータを変えながら繰り返し実行する場合は,-a で1つのファイルに追記します.
for lr in 0.1 0.01 0.001; do
echo "=== lr=${lr} ===" | tee -a sweep.log
python -u train.py --lr "${lr}" 2>&1 | tee -a sweep.log
done例 6 (パイプラインの中間結果を保存する) 前処理をパイプでつないでいるとき,途中段階のデータをファイルに落としておくと,後から件数や中身を検証できます.
zcat access.log.gz \
| grep -v "healthcheck" \
| tee filtered.log \
| awk '{print $1}' | sort | uniq -c | sort -rn | head