BashやLinuxのシェルスクリプトでバッチ処理やコマンドを実行する際、避けて通れないのが「標準エラー出力(stderr)」のリダイレクトです。
「標準出力と標準エラー出力をまとめて1つのログに保存したい」「エラーメッセージだけを別ファイルに切り出したい」「不要なエラー出力を /dev/null で消し去りたい」など、ログ設計や自動化においてリダイレクトの正確な知識は不可欠です。
しかし実務では、「command > file 2>&1 と書くべきところを command 2>&1 > file と書いてしまい、画面にエラーが漏れ出る」「パイプラインで後続の grep にエラーが渡らない」「sudo 実行時にリダイレクトが Permission denied になる」といったトラブルが頻発します。
この記事では、ファイルディスクリプタ(FD)の基礎から、個別保存・統合・破棄の全パターン、リダイレクト順序による決定的な動作の違い(内部ポインタの図解付き)、Bash拡張記法(&> / |&)とPOSIX互換性、そして実務で即コピペできる実践スクリプトまで、現場目線で徹底解説します。
【早見表】標準出力・標準エラー出力リダイレクト チートシート
日常の開発・運用で頻出するリダイレクト記法と目的別の使い分け一覧です。
| 記法 | 動作内容 | 主な用途・互換性 |
|---|---|---|
cmd > out.log |
標準出力(FD 1)のみを上書き保存 | 通常ログの保存(POSIX準拠) |
cmd 2> err.log |
標準エラー出力(FD 2)のみを上書き保存 | エラーログの分離(POSIX準拠) |
cmd > out.log 2> err.log |
標準出力を out.log、標準エラーを err.log へ個別保存 |
正常系と異常系の完全分離(POSIX準拠) |
cmd > all.log 2>&1 |
標準出力と標準エラー出力を1つのファイルへ統合保存 | バッチ・cronログの定番(POSIX準拠) |
cmd >> all.log 2>&1 |
標準出力と標準エラー出力を1つのファイルへ追記保存 | ログ追記(POSIX準拠) |
cmd &> all.logcmd >& all.log |
標準出力・標準エラー出力を1つのファイルへ統合保存(短縮記法) | Bash / Zsh 拡張(POSIX非推奨) |
cmd > /dev/null 2>&1 |
すべての出力(通常・エラー)を完全に破棄 | 画面・ログ不要なコマンド実行(POSIX準拠) |
cmd 2> /dev/null |
標準エラー出力のみを破棄(通常出力はそのまま表示) | 不要な警告メッセージの抑制(POSIX準拠) |
cmd 2>&1 | grep word |
標準エラー出力を標準出力に合流させてパイプに流す | エラー文言のgrep検索(POSIX準拠) |
cmd |& grep word |
標準出力と標準エラー出力をまとめてパイプに流す(短縮記法) | Bash 4.0+ / Zsh 拡張 |
1. ファイルディスクリプタ(FD)の基本と仕組み
リダイレクトを正確に理解するための基礎概念が、Linux/UNIXにおけるファイルディスクリプタ(File Descriptor / FD)です。
プロセスが起動されると、OSは標準で以下の3つの入出力ストリームを割り当てます。
| FD番号 | ストリーム名称 | デフォルトの接続先 | 主な役割 |
|---|---|---|---|
0 |
標準入力 (stdin) |
キーボード / 端末入力 | プログラムへの入力データを受け取る |
1 |
標準出力 (stdout) |
ターミナル画面(コンソール) | コマンドの正常な実行結果・データを出力 |
2 |
標準エラー出力 (stderr) |
ターミナル画面(コンソール) | エラー通知、警告、プログレスバー等を出力 |
リダイレクト記号 > の直前にある数字は、このFD番号を指定しています。FD番号を省略した > file は、デフォルトで 1> file(標準出力のリダイレクト)とみなされます。
# 以下の2行は完全に同一の処理
command > output.log
command 1> output.log
# 標準エラー出力(FD 2)を指定してリダイレクト
command 2> error.log
2. 標準エラー出力のリダイレクト基本パターン
パターン1: 標準エラー出力のみをファイルに保存(2> / 2>>)
標準出力はそのまま端末画面に表示させつつ、発生したエラーメッセージだけをファイルに記録したい場合は 2> を使います。
# 上書き保存
ls /tmp /not_exist_dir 2> error.log
# 追記保存(既存ログの末尾に追加)
ls /tmp /not_exist_dir 2>> error.log
このコマンドを実行すると、存在する /tmp の一覧は画面に出力され、存在しない /not_exist_dir に対するエラーメッセージのみが error.log に書き込まれます。
パターン2: 標準出力と標準エラー出力を別々のファイルへ保存
正常系の処理結果データと、異常系のエラーログを完全に分離して保存したい場合の王道パターンです。
# 上書きで個別保存
command > stdout.log 2> stderr.log
# 追記モードで個別保存
command >> stdout.log 2>> stderr.log
パターン3: 標準出力と標準エラー出力を1つのファイルに統合保存(2>&1)
cronの実行ログや自動デプロイスクリプトなど、時系列の実行ログをまるごと1つのファイルに残したい場合に最も広く使われる記法です。
# 上書きで統合保存
command > all_output.log 2>&1
# 追記モードで統合保存
command >> all_output.log 2>&1
3. 【超重要】リダイレクト順序による決定的な違いと仕組み
なぜ command > file 2>&1 と書き、command 2>&1 > file ではダメなのでしょうか?
この違いは、Bashがリダイレクトを「左から右へ順番に評価・適用する」という仕様にあります。
正しい順序: command > file 2>&1 の内部動作
シェルがコマンドを実行する際、ファイルディスクリプタの接続先は以下のように遷移します。
【ステップ1: 初期状態】
FD 1 (stdout) ----> [ 端末画面 (Terminal) ]
FD 2 (stderr) ----> [ 端末画面 (Terminal) ]
【ステップ2: > file.log (1> file.log) を評価】
FD 1 (stdout) ----> [ file.log ]
FD 2 (stderr) ----> [ 端末画面 (Terminal) ]
【ステップ3: 2>&1 を評価】
※「FD 2 の出力先を、現在の FD 1 の出力先(file.log)と同じにする」
FD 1 (stdout) ----> [ file.log ]
FD 2 (stderr) ----> [ file.log ]
この結果、標準出力も標準エラー出力も同じ file.log へ書き込まれます。
間違った順序: command 2>&1 > file の内部動作
順序を逆にしてしまうと、以下のような挙動になります。
【ステップ1: 初期状態】
FD 1 (stdout) ----> [ 端末画面 (Terminal) ]
FD 2 (stderr) ----> [ 端末画面 (Terminal) ]
【ステップ2: 2>&1 を先に評価】
※「FD 2 の出力先を、現在の FD 1 の出力先(端末画面)と同じにする」
FD 1 (stdout) ----> [ 端末画面 (Terminal) ]
FD 2 (stderr) ----> [ 端末画面 (Terminal) ]
【ステップ3: > file.log を評価】
※「FD 1 の出力先を file.log に変更する」
FD 1 (stdout) ----> [ file.log ]
FD 2 (stderr) ----> [ 端末画面 (Terminal) ] <-- 端末のまま取り残される!
# ❌ 失敗例:標準エラー出力が端末画面にそのまま漏れ出る
ls /tmp /not_exist 2>&1 > result.log
# 画面に "ls: cannot access '/not_exist': No such file or directory" が表示される
「2>&1 は “その時点におけるFD 1の行き先” をコピーするポインタ複製である」という本質を覚えておけば、順序で迷うことはなくなります。
4. /dev/null への出力破棄テクニック
/dev/null は書き込まれたデータをすべて破棄する特殊なキャラクタデバイス(ブラックホール)です。
標準エラー出力のみを破棄(2> /dev/null)
「コマンドの正常な出力結果だけを取り出したい」「権限不足の警告など、想定内のノイズを消したい」場合に利用します。
# findコマンド実行時、アクセス権のないディレクトリのエラーメッセージを捨てる
find / -name "*.conf" 2> /dev/null
# grep実行時の警告を抑制
grep -rn "API_KEY" /var/www/ 2> /dev/null
全出力を完全に破棄(> /dev/null 2>&1)
出力を一切表示せず、コマンドの終了ステータス(戻り値 $?)のみを利用したい場合に定番のイディオムです。
# コマンドの存在確認(出力は一切出さず、ifの成否判定だけを行う)
if command -v docker > /dev/null 2>&1; then
echo "Docker is installed"
fi
# cron設定でのサイレント実行(不要なメール送信を防止)
0 3 * * * /usr/local/bin/backup.sh > /dev/null 2>&1
5. Bash拡張記法(&> と |&)とPOSIX互換性
BashやZshなどの高機能シェルには、リダイレクトを簡潔に書くための短縮構文が用意されています。
統合リダイレクトの短縮形: &>
> file 2>&1 の代わりに &> file と記述できます(追記の場合は &>> file)。
# Bash拡張構文
command &> all.log
command &>> all.log
パイプライン統合の短縮形: |&
2>&1 | の代わりに |& を使うことで、標準出力と標準エラー出力の両方をまとめてパイプで後続コマンドに渡せます(Bash 4.0以降)。
# POSIX準拠記法
make 2>&1 | grep "error"
# Bash 4.0+ 短縮記法
make |& grep "error"
POSIX準拠(ポータビリティ)との比較と指針
| 機能 | POSIX sh 互換構文(推奨) | Bash専用拡張構文 |
|---|---|---|
| ファイルへ統合 | cmd > file 2>&1 |
cmd &> file |
| ファイルへ追記統合 | cmd >> file 2>&1 |
cmd &>> file |
| パイプへ統合 | cmd 2>&1 | cmd2 |
cmd |& cmd2 |
※ #!/bin/sh で実行されるスクリプトや Alpine Linux(BusyBox / ash)、Debian/Ubuntu の /bin/sh(dash)では &> や |& は構文エラー(syntax error)になります。
スクリプトの移植性を保つためには、常に > file 2>&1 を使用するのが安全なベストプラクティスです。
6. 実務で役立つ実践リダイレクトパターン集(コピペ可)
現場のシェルスクリプト開発でそのままコピー&ペーストして使える実務テンプレート集です。
実践例1: スクリプト全体の出力を一括でログファイルへリダイレクト(exec)
スクリプト内の全コマンドに個別にリダイレクトを書くのではなく、先頭で exec を宣言してスクリプト全体の出力をログへ自動集約します。
#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="/var/log/my-app"
mkdir -p "${LOG_DIR}"
LOG_FILE="${LOG_DIR}/batch_$(date +%Y%m%d).log"
# スクリプト全体の stdout と stderr をログファイルと端末画面の両方に出力
exec > >(tee -a "${LOG_FILE}") 2>&1
echo "[$(date '+%Y-%m-%d %H:%M:%S')] === バッチ処理開始 ==="
# ここに書かれたコマンドの出力は自動的にすべてログへ記録される
ls -la /tmp
systemctl status nginx --no-pager
echo "[$(date '+%Y-%m-%d %H:%M:%S')] === バッチ処理完了 ==="
実践例2: 標準出力はパイプへ流し、標準エラー出力のみログファイルへ退避
JSONやCSVなどのデータ本体は後続のパーサー(jq や awk)へ流しつつ、エラーメッセージのみを別ログに退避するテクニックです。
#!/usr/bin/env bash
set -euo pipefail
ERROR_LOG="/tmp/curl_errors.log"
# stdoutはjqへ渡し、stderrのみERROR_LOGへ書き出す
if response=$(curl -sS -f "https://api.example.com/data" 2> "${ERROR_LOG}" | jq -r '.data[]'); then
echo "取得データ:"
echo "${response}"
else
echo "API取得失敗。エラーログを確認してください: ${ERROR_LOG}" >&2
cat "${ERROR_LOG}" >&2
fi
実践例3: 標準出力と標準エラー出力を入れ替える(FD 3を使ったスワップ)
「標準エラー出力だけをパイプで grep に流してフィルタリングし、標準出力はそのまま画面に出したい」という高度なケースでは、一時的なファイルディスクリプタ(FD 3)を使ってストリームを入れ替えます。
#!/usr/bin/env bash
# stdout と stderr をスワップして stderr のみ grep に渡す
# 1) FD 3 を FD 1 に複製
# 2) FD 1 を FD 2 に複製
# 3) FD 2 を FD 3 に複製
# 4) FD 3 を閉じる
{
echo "これは通常メッセージ (stdout)"
echo "CRITICAL: データベース接続エラー (stderr)" >&2
} 3>&1 1>&2 2>&3 3>&- | grep "CRITICAL"
実践例4: sudo 実行時のリダイレクト権限エラー回避(sudo tee)
sudo command > /etc/config.conf 2>&1 と書くと、リダイレクト処理自体は一般ユーザー権限で行われるため Permission denied エラーになります。これを防ぐには sudo tee を使用します。
# ❌ 失敗する例(リダイレクトが一般ユーザー権限で実行される)
sudo echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/99-custom.conf 2>&1
# ✅ 解決策1: sudo tee を使用(推奨)
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-custom.conf > /dev/null
# ✅ 解決策2: sudo bash -c でシェルごと昇格
sudo bash -c 'echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/99-custom.conf' 2>&1
7. 実務でよくある落とし穴とアンチパターン
落とし穴1: 同じファイルに > file 2> file と別々に書き込む
# ❌ アンチパターン(ファイルの競合・データ破損)
command > output.log 2> output.log
原因:
この書き方をすると、FD 1 と FD 2 がそれぞれ独立して output.log を開きます。2つのファイルポインタが互いの書き込み位置を共有しないため、先頭から互いに上書きし合い、ログの文字化けやデータ消失が発生します。
対策: 必ず command > output.log 2>&1(ポインタ複製)を使用してください。
落とし穴2: パイプラインで標準エラー出力が無視される
# ❌ エラーメッセージが grep をすり抜けて画面に出てしまう
cat /not_exist_file | grep "No such"
原因:
パイプ | は標準出力(FD 1)のみを次のコマンドの標準入力(FD 0)に繋ぎます。標準エラー出力(FD 2)はパイプを通過せず、直接端末画面に出力されます。
対策: cat /not_exist_file 2>&1 | grep "No such" と記述して、エラー出力を標準出力に合流させてからパイプに流します。
落とし穴3: cron 設定で 2>&1 を書き忘れてメール送信が多発
cronで定期実行するタスクにおいて、リダイレクトを指定しないか > /dev/null だけにしておくと、エラーが発生するたびにローカルMTA(Postfix等)が管理者に通知メールを送信し、メールキューの肥大化やディスク圧迫を招きます。
対策: 破棄する場合は > /dev/null 2>&1、記録する場合は >> /var/log/cron.log 2>&1 を必ずセットで記述してください。
まとめ
Bashの標準エラー出力リダイレクトを安全に使いこなすための重要ポイントの振り返りです。
- ファイルディスクリプタ:
0は stdin、1は stdout、2は stderr - 個別保存:
command > out.log 2> err.log - 統合保存:
command > all.log 2>&1(順序は必ず> fileが先、2>&1が後) - 不要出力破棄: エラーのみ破棄は
2> /dev/null、全破棄は> /dev/null 2>&1 - ポータビリティ:
&>や|&は Bash拡張。sh互換が必要なスクリプトでは> file 2>&1を使う - アンチパターン防止:
> file 2> fileはポインタ競合を起こすため絶対に使わない
リダイレクトの仕組みと評価順序を理解しておくことで、ログの欠落や競合を防ぎ、堅牢でデバッグしやすいシェルスクリプトやバッチ処理を構築できます。
関連おすすめ記事
- 入出力とプロセス制御|リダイレクト/パイプ/サブシェル
- teeコマンドの使い方完全ガイド|標準出力とファイルへの同時書き込み・sudo連携・オプション解説
- 【Bash】ヒアドキュメントの使い方まとめ|複数行出力・変数展開の制御・sudo tee連携を徹底解説
- Bashのsetコマンドを使いこなす方法とオプション一覧 | set -euo pipefail の効き方
- Permission deniedの原因と直し方|6つの切り分け手順
- exec コマンドとは|プロセス置換・FDリダイレクトの実用例【Bash/Linux】
- シェルスクリプトの引数処理まとめ|位置パラメータ・デフォルト値・$@と$*の違いを徹底解説
- 【Bash】シェルスクリプトでの配列の使い方まとめ|宣言・追加・ループ処理・要素数を徹底解説

コメント