前回の記事(入門20)では、これまでに学んだ curl や jq を結集し、シェルスクリプトからGemini APIを自動呼び出しする「AI自動応答スクリプト」を構築しました。
スクリプトが実用的になるにつれて、次に重要となってくるのが「実行結果のログを確実に記録し、エラーを適切に分離・管理すること」です。
今後予定している「cronによる定期自動実行」では、人の目が届かないバックグラウンドで処理が動くため、ログが適切に残っていなければ、成功したのか失敗したのかすら把握できなくなってしまいます。
そこで今回は、安全な自動運用の基盤となる「標準入力・標準出力・標準エラー出力(ファイルディスクリプタ)」の仕組みから、各種リダイレクト記法(2>&1 など)、そして画面表示とファイル保存を両立する「tee コマンド」の実務テクニックまでを解説します!
3つの標準ストリームとファイルディスクリプタ
LinuxやmacOSなどのUnix系OSでは、プログラムが動作する際に「3つの基本的なデータの出入り口(標準ストリーム)」が自動的に用意されます。
OS内部では、これらを「ファイルディスクリプタ(FD: File Descriptor)」と呼ばれる番号(0, 1, 2)で管理しています。
| 番号(FD) | ストリーム名 | 英語名 | デフォルトの接続先 | 主な役割 |
|---|---|---|---|---|
| 0 | 標準入力 | stdin | キーボード / パイプ | プログラムが読み込む入力データ |
| 1 | 標準出力 | stdout | 画面(ターミナル) | コマンドの正常な実行結果や通常のメッセージ |
| 2 | 標準エラー出力 | stderr | 画面(ターミナル) | エラーメッセージや警告、ログ情報 |
普通にターミナルでコマンドを実行していると、正常な結果(標準出力)もエラーメッセージ(標準エラー出力)も同じ画面に表示されるため、違いを意識しづらいかもしれません。
しかし、「なぜわざわざ出力が1番と2番の2つに分かれているのか?」を理解することが、シェルスクリプト上達の鍵となります。
例えば、コマンドをパイプ(|)で繋いだ場合、パイプ経由で次のコマンドに渡されるのは「標準出力(1番)」のみです。もしエラーメッセージまで標準出力に混ざってしまうと、後続の grep や jq などのパース処理が壊れてしまいます。
「正常なデータ処理の流れ」と「異常を知らせる警告」を分けるために、標準出力と標準エラー出力は独立した出口として設計されています。
リダイレクト(> / >>)の基本とエラー分離テクニック
コマンドの出力先を画面からファイルへ切り替える操作を「リダイレクト(Redirect)」と呼びます。現場で多用される主要パターンを順に見ていきましょう。
上書き(>)と追記(>>)の使い分け
数値(ファイルディスクリプタ)を省略して単に > または >> と書いた場合、標準出力(1番)が対象となります。
# 上書き保存(既存ファイルの中身は消去され、新しく作り直される)
echo "処理開始" > app.log
# 追記保存(既存ファイルの末尾に新しい行を追加する)
echo "処理完了" >> app.log
日々の運用ログを残す場合は、過去の履歴が消えてしまわないよう、基本的に追記(>>)を使用します。
標準エラー出力のリダイレクト(2> / 2>>)
エラーメッセージだけをファイルへ保存したい場合は、リダイレクト記号の前に「2」を明記します。
# 存在しないファイルを指定して出力されるエラーメッセージを error.log に保存
ls /path/to/nonexistent 2> error.log
# 正常結果は success.log、エラーメッセージは error.log に分離して保存
my_batch_command >> success.log 2>> error.log
上記のように >> success.log 2>> error.log と記述すると、正常データと障害ログを別々のファイルへ振り分けることができます。
出力を1つにまとめる「2>&1」の仕組みと評価順序の注意点
「正常な結果もエラーも、発生順にすべて1つのログファイルにまとめたい」というケースで登場するのが、「2>&1」という記法です。
# 標準出力と標準エラー出力をすべて app.log にまとめる(正しい書き方)
my_batch_command >> app.log 2>&1
この 2>&1 は、「標準エラー出力(2番)の送り先を、標準出力(1番)と同じ場所へ合流させる」という意味を持っています(&1 のアンド記号は「ファイル名ではなくファイルディスクリプタの1番」を表します)。
ここで非常に重要な「評価順序の注意点」があります。リダイレクトは「左から右へ順番に評価される」というルールがあります。
# 【正しい順序】
my_batch_command >> app.log 2>&1
# 1. まず標準出力(1番)の向き先が「app.log」に設定される
# 2. 次に標準エラー出力(2番)が、現在の標準出力の向き先(app.log)に合流する
# ➔ 結果:両方とも app.log に記録される
# 【誤った順序の落とし穴】
my_batch_command 2>&1 >> app.log
# 1. まず標準エラー出力(2番)が、現在の標準出力の向き先(画面)に設定される
# 2. その後、標準出力(1番)だけが「app.log」に向き先変更される
# ➔ 結果:エラーメッセージはファイルに入らず、画面にそのまま出てしまう!
「ファイルの指定(>> app.log)を先に書き、その後に合流(2>&1)を書く」という順番を意識するのが基本です。
※なお、Bashでは &>> app.log という短縮記法も用意されていますが、古いシェルや純粋なPOSIX sh 環境ではエラーになる場合があるため、互換性を重視する場合は >> file 2>&1 を使っておくのが手堅い選択です。
不要な出力を破棄する(/dev/null)
「コマンドの終了ステータス(成功か失敗か)だけが必要で、画面出力やログファイルは残したくない」というサイレント実行では、Linuxの特殊デバイス /dev/null に出力を流し込みます。
# 画面表示もエラーメッセージも破棄し、終了ステータス(存在有無)のみを判定
if ls /tmp/app_lock.pid > /dev/null 2>&1; then
echo "ロックファイルが存在するため処理を中断します"
exit 1
fi
/dev/null に送られたデータはそのまま消滅するため、画面に不要なメッセージを出力せずに判定を行いたい場面で重宝します。
画面表示とログ保存を両立する「teeコマンド」の実践
リダイレクトは便利ですが、実務でスクリプトを手動実行する際に1つの課題に直面します。>> app.log 2>&1 と書いてしまうと、出力がすべてファイルに書き込まれるため、画面には何も表示されず「今どこまで処理が進んでいるのか」が分からなくなってしまう点です。
そこで活躍するのが「tee(ティー)コマンド」です。
teeコマンドの仕組み(T字ジョイントのイメージ)
配管工事で使われる「T字型の継手(パイプ)」が名前の由来です。
標準入力からデータを受け取り、「①ターミナル画面(標準出力)」と「②指定したログファイル」の2箇所へ同時にデータを分岐して流し込みます。
# 画面にメッセージを表示しつつ、app.log にも保存する
echo "バックアップ処理を開始します" | tee app.log
必須オプション「-a」(追記モード)の重要性
tee コマンドをログ記録で使用する際は、「-a(--append)」オプションの付与が推奨されます。
# 【注意】オプションなしだと、実行するたびにファイルを新規上書きしてしまう!
echo "ログ1" | tee app.log
echo "ログ2" | tee app.log # ➔ 「ログ1」が消えて「ログ2」だけになってしまう!
# 【正解】「-a」オプションで既存のログファイルの末尾に追記する
echo "ログ1" | tee -a app.log
echo "ログ2" | tee -a app.log # ➔ 過去のログを残したまま追記される
標準エラー出力も一緒にteeへ流すテクニック
パイプ(|)は通常「標準出力(1番)」のみを後続のコマンドに渡します。
そのため、単に my_command | tee -a app.log と実行しただけでは、エラーメッセージ(標準エラー出力)はファイルに保存されず、画面に素通りしてしまいます。
エラーも含めて画面とファイルの両方に残すには、先ほど学んだ 2>&1 をパイプの手前に挟みます。
# 標準エラー出力を標準出力に合流させてから tee に渡す
my_command 2>&1 | tee -a app.log
この1行によって、「正常な出力もエラーメッセージも、画面でリアルタイムに確認しながら、app.log へ追記保存する」というロギングが実現できます。
現場で役立つ実践ログ出力関数(logger)の構築
これまでに学んだ知識を統合し、実務スクリプトですぐに導入できる「タイムスタンプ付きの共通ログ関数」を作成してみましょう。
画面には見やすく表示しつつ、指定のログファイルに自動追記するサンプルスクリプト(log_demo.sh)です。
#!/bin/bash
# エラーハンドリング設定(入門12で学習)
set -euo pipefail
# ログファイルの保存先定義
LOG_DIR="./logs"
LOG_FILE="${LOG_DIR}/backup_$(date +'%Y%m%d').log"
# ログ保存ディレクトリが存在しない場合は作成
mkdir -p "${LOG_DIR}"
# ---------------------------------------------------------------------
# 共通ロギング関数
# ---------------------------------------------------------------------
log_message() {
local level="$1"
local message="$2"
local timestamp
timestamp=$(date +'%Y-%m-%d %H:%M:%S')
# タイムスタンプとログレベルを整形して画面とファイルへ同時出力
echo "[${timestamp}] [${level}] ${message}" | tee -a "${LOG_FILE}"
}
log_info() {
log_message "INFO" "$1"
}
log_warn() {
log_message "WARN" "$1"
}
log_error() {
# エラー時は画面の標準エラー出力(FD 2)に向けて流す
log_message "ERROR" "$1" >&2
}
# ---------------------------------------------------------------------
# メイン処理のシミュレーション
# ---------------------------------------------------------------------
log_info "=== 定期バッチバックアップ処理を開始します ==="
# 模擬処理1:ディスク容量の確認
log_info "ディスク容量をチェック中..."
df -h . | awk 'NR==2 {print "使用量: " $3 " / 空き容量: " $4 " (" $5 ")"}' | tee -a "${LOG_FILE}"
# 模擬処理2:重要ディレクトリのアーカイブ
TARGET_DIR="./important_data"
if [ -d "${TARGET_DIR}" ]; then
log_info "ディレクトリ ${TARGET_DIR} のバックアップを作成します。"
else
log_warn "対象ディレクトリ ${TARGET_DIR} が見つかりません。スキップします。"
fi
# 模擬処理3:外部コマンドの実行結果をキャプチャ
log_info "外部サービスとの接続確認を実行中..."
# 成功時も失敗時もまとめてログに記録するパターン
if ping -c 1 127.0.0.1 > /dev/null 2>&1; then
log_info "ネットワーク導通確認:正常"
else
log_error "ネットワーク導通確認:通信失敗"
fi
log_info "=== すべての処理が正常に完了しました(ログ保存先: ${LOG_FILE}) ==="
スクリプトの実行と動作確認
実行権限を付与してスクリプトを実行してみましょう。
chmod +x log_demo.sh
./log_demo.sh
ターミナル画面には以下のようにタイムスタンプ付きでリアルタイムに進捗が表示されます。
[2026-09-17 12:00:01] [INFO] === 定期バッチバックアップ処理を開始します ===
[2026-09-17 12:00:01] [INFO] ディスク容量をチェック中...
使用量: 45Gi / 空き容量: 180Gi (20%)
[2026-09-17 12:00:01] [WARN] 対象ディレクトリ ./important_data が見つかりません。スキップします。
[2026-09-17 12:00:01] [INFO] 外部サービスとの接続確認を実行中...
[2026-09-17 12:00:01] [INFO] ネットワーク導通確認:正常
[2026-09-17 12:00:01] [INFO] === すべての処理が正常に完了しました(ログ保存先: ./logs/backup_20260917.log) ===
そして、生成された ./logs/backup_20260917.log の中身を確認すると、画面に出力された内容と同一のログが保存されていることが確認できます。
まとめ
今回は、シェルスクリプト運用で欠かせない標準ストリームの仕組みから、各種リダイレクト、そして画面とファイルの両方に同時に記録する tee コマンドのテクニックを解説しました。
- 3つの標準ストリーム:標準入力(0)、標準出力(1)、標準エラー出力(2)の役割を意識する。
- リダイレクトの評価順序:すべて1つにまとめる際は
>> file 2>&1の順序を守る。 - teeコマンドの活用:画面でリアルタイム確認しつつ、
tee -aでファイルに安全に追記保存する。
次回の記事では、このログ設計を活用し、サーバー上で指定したスケジュール通りにスクリプトを安全に自動実行する「cronによる定期自動実行」をお届けします!

コメント