Linuxのstderrを知らずにエラーが消えていた日の話|2>&1の意味を理解するまで3年かかった経験

HOMEリナックスマスター.JP 公式ブログLinux学習ガイド > Linuxのstderrを知らずにエラーが消えていた日の話|2>&1の意味を理解するまで3年かかった経験
宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
「ログを見ても何も書いていないのに、なぜかスクリプトが正常に動いていない気がする…。」
cronで定期実行しているシェルスクリプトのログを確認しても、エラーの痕跡がどこにも残らない。正常終了しているのか、途中でエラーが起きているのかすら分からない。そんな状況に陥ったことはありませんか。

私はSE時代の2003年頃、まさにこの謎に3年間悩まされ続けました。20年以上Linuxサーバーを運用してきた経験から正直に言うと、あの頃の私は「リダイレクトしているからログが取れているはず」という思い込みで、エラー出力(stderr)の存在をまったく意識していませんでした。
この記事では、その失敗談と、標準入出力(stdin・stdout・stderr)の本質的な理解についてお伝えします。コマンドを毎日打っているエンジニアほど意外と見落としやすいポイントで、私のセミナーでも繰り返し質問が上がるテーマです。

この記事のポイント

・Linuxのコマンドは「stdout(標準出力)」と「stderr(標準エラー出力)」の2本の出力経路を持つ
・「>」だけではstdoutしか記録されない。stderrも記録するには「2>&1」が必要
・cronで動かすスクリプトのエラーが"消える"原因の多くはこの仕組みの理解不足
・「エラーが出ていないから正常」は危険。stderrの行き先を常に意識する習慣が現場を変える


Linuxのstderrを知らずにエラーが消えていた日の話|2>&1の意味を理解するまで3年かかった経験
「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

「ログをリダイレクトしているのにエラーの痕跡がない」という謎

SE時代に私が担当していたのは、夜間バッチ処理を管理するサーバーでした。定期的にデータファイルを処理するシェルスクリプトを書き、cronで毎晩実行していました。ログを残すために、cronの設定でこう書いていました。

/usr/local/bin/process_data.sh > /var/log/batch/process.log

これで完璧なはずでした。実行するたびにログファイルが更新され、正常終了のメッセージが記録される。「ちゃんとログが取れている」と安心していました。

ところがある日、処理したはずのデータが正しく反映されていないことに気づきます。ログを確認しても、いつも通りの「処理完了」の文字があるだけ。エラーはどこにも書かれていません。

当時の私はこう思いました。「エラーが出ていないなら、スクリプトの問題ではなく、データの問題かもしれない」と。結果として、スクリプトとはまったく関係のないデータをひたすら調べ続けるという、見当違いの調査を3週間近く続けることになりました。

真相が分かったのは、先輩エンジニアが隣で偶然同じスクリプトを手動実行したときでした。ターミナルに赤いエラーメッセージが表示されたのです。

「あれ、このコマンド、Permission deniedって出てるけど?」

私の顔が一瞬止まりました。ログファイルには何も書かれていないのに、ターミナルの画面には明らかにエラーが出ている。「なぜファイルに記録されないのか」という謎の答えが「stderr(標準エラー出力)」でした。あの瞬間の衝撃は今でも覚えています。

Linuxの標準入出力(stdin・stdout・stderr)の本質を正しく理解する

Linuxでは、すべてのプロセスに対して3本の通信経路が用意されています。これを「標準入出力(Standard I/O)」と呼びます。

stdin(標準入力、ファイルディスクリプタ0):プロセスへのデータの入口。キーボードからの入力がデフォルト
stdout(標準出力、ファイルディスクリプタ1):正常な処理結果の出口。ターミナル画面がデフォルト
stderr(標準エラー出力、ファイルディスクリプタ2):エラーメッセージの出口。こちらもターミナル画面がデフォルト

この「2本の出力経路が別々に存在する」という設計が重要です。たとえばlsコマンドを実行したとき、存在するファイルの一覧はstdoutへ、存在しないパスへのエラーメッセージはstderrへと、それぞれ別々の経路に流れます。

通常、stdoutとstderrは両方ともターミナルの画面に表示されます。だから手動で実行したときは、正常な出力もエラーも混在して画面に見える。それが「ログに記録しているはずなのに、cronから実行するとエラーが消える」という謎の原因です。

cronから実行すると、ターミナルに接続していないため、リダイレクトで指定したファイル以外に出力する先がありません。私がやっていた> /var/log/batch/process.logという書き方は「stdoutをファイルにリダイレクト」する指示です。stderrには一切触れていないため、cronから実行するとstderrはどこにも記録されずに消えていたのです。

Linuxの設計としてはこれは正しい動作です。stdoutとstderrを分けているのには理由があります。自動化スクリプトで「正常な処理結果」と「エラーメッセージ」を分けて扱えるように、意図的に別々の経路を設けているのです。その設計を知らずに使っていた私は、エラーを意図せず捨て続けていました。

「エラー出力が動かない」「ログに記録されない」トラブルの対処法

cronで実行するスクリプトのエラーが記録されない場合、原因はほぼ確実に「stderrをリダイレクトしていない」ことです。対処法はシンプルです。

基本:stderrも同じファイルに記録する

# stdoutだけをログに残す(stderrは消える)--- よくある間違い /usr/local/bin/process_data.sh > /var/log/batch/process.log # stdoutとstderrを同じファイルに記録する --- 正しい書き方 /usr/local/bin/process_data.sh > /var/log/batch/process.log 2>&1 # cronのcrontab設定例(stderrも含めて追記モードで記録する) 0 2 * * * /usr/local/bin/process_data.sh >> /var/log/batch/process.log 2>&1

2>&1の意味を分解すると、「ファイルディスクリプタ2(stderr)を、ファイルディスクリプタ1(stdout)が指している場所と同じ場所に向ける」という指示です。

重要なのは順序です。> /var/log/batch/process.log 2>&1と書いた場合、まずstdoutをログファイルに向け、次にstderrも同じログファイルに向ける、という2段階の操作になります。逆に2>&1 > /var/log/batch/process.logと書くと、「stderrをその時点のstdout(まだターミナル)に向けてから、stdoutをファイルに向ける」という操作になり、stderrがファイルに記録されません。この順序の違いで動作が変わることを知らずに悩むエンジニアを、私はセミナーで何人も見てきました。

stderrだけを別ファイルに分けて記録する場合

# stdoutとstderrを別々のファイルに記録する /usr/local/bin/process_data.sh > /var/log/batch/out.log 2> /var/log/batch/err.log # cronの設定例(追記モードで分けて記録) 0 2 * * * /usr/local/bin/process_data.sh >> /var/log/batch/out.log 2>> /var/log/batch/err.log # systemctl status sshd # エラーログに内容があるか確認(サイズを確認) # ls -lh /var/log/batch/err.log

エラーファイルを分けると、「エラーファイルが空であれば正常」という判定が一瞬でできます。監視スクリプトからファイルサイズをチェックするだけでアラートを上げられるため、自動監視設計との相性もよい方法です。

私が現場で指導してきた中で最もよく見る失敗は、「スクリプトが正常終了しているからエラーはない」と判断してしまうパターンです。シェルスクリプトは、内部でエラーが起きても終了コード0を返すことがあります。特にset -e(エラー時に即終了する設定)を書いていないスクリプトは、エラーを無視して最後まで走りきることも珍しくありません。stderrを記録していなければ、そのエラーは永遠に気づかれません。

現場でよく使うstderrとリダイレクトの実践パターン

セミナーで3,100名以上を指導してきた中で、「cronとログ設計」に関する質問は非常に多いテーマのひとつです。現場でよく使うパターンをいくつか紹介します。

パターン1:すべて1ファイルにまとめる(最もシンプル)

スクリプトの出力もエラーも同じファイルに追記していく方法です。障害調査のとき、1つのファイルを時系列で追えるのが利点です。最初に覚えるならこれが確実です。

0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup/backup.log 2>&1

>>(追記)にするのが重要です。>(上書き)にすると前回実行のログが消えてしまうため、cron定期実行には追記が基本です。

パターン2:エラーだけを別ファイルに分ける

正常出力が大量にある処理では、エラーファイルを分けると異常検知が楽になります。監視ツールからエラーファイルのサイズや行数を確認するだけで、「今日のバッチは正常だったか」をすぐ判定できます。

0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup/out.log 2>> /var/log/backup/err.log

パターン3:スクリプト内部でstderrに意図的に書き込む

自分でシェルスクリプトを書くとき、エラーメッセージを明示的にstderrに出力する習慣も大切です。echoコマンドはデフォルトでstdoutに出力するため、エラーメッセージをstderrに出すには>&2を使います。

# エラーメッセージをstderrに出力するシェルスクリプトの書き方 #!/bin/bash BACKUP_DIR="/var/backup/data" if [ ! -d "$BACKUP_DIR" ]; then # stdoutではなくstderrにエラーメッセージを出力する echo "エラー: バックアップディレクトリが存在しません: $BACKUP_DIR" >&2 exit 1 fi echo "バックアップ開始: $(date)" tar czf /var/backup/archive_$(date +%Y%m%d).tar.gz "$BACKUP_DIR" echo "バックアップ完了: $(date)"

この書き方をしておくと、スクリプトを呼び出す側が「stdoutとstderrを分けて処理する」設計に自然と対応できます。正常な進捗メッセージはout.logへ、エラーメッセージはerr.logへと、呼び出し元がコントロールできるのです。

パターン4:teeコマンドで画面表示とファイル記録を同時に行う

手動でスクリプトを動かしながらログにも残したいとき、teeコマンドが便利です。

/usr/local/bin/process_data.sh 2>&1 | tee /var/log/batch/process.log

stdoutとstderrをまとめてからteeに渡すことで、画面にもリアルタイム出力しつつ、同じ内容をファイルにも記録できます。手動実行でのデバッグ時に重宝します。

「エラーを無音で捨てない」という習慣が現場を変える

私が20年以上サーバーを運用してきた経験から言うと、シニアなエンジニアと新人エンジニアの最大の差のひとつが「エラーの捨て方」に対する姿勢です。

新人の頃は「エラーが出なければOK」と考えがちです。でも実際には、「エラーが出ていない」と「エラーが出ていないことを確認した」は、まったく別のことです。前者はstderrがどこかに消えているだけかもしれない。後者は、エラーログが空であることを意図的に確認した状態です。

私のセミナーでは、「cronで走らせるスクリプトには必ず2>&1を付けること」を鉄則として教えています。付けないことのリスクは、「エラーが出ていても気づけない状態を作り続けること」だからです。

また、/dev/nullにstderrを捨てるリダイレクト(2>/dev/null)を安易に使うことも避けるべきです。「このエラーは無視してよい」という確信がある場合のみ使う。理由も分からないまま2>/dev/nullと書いてしまうと、将来の自分や後任者が原因調査できなくなります。私は現場でこの理由で障害調査が迷宮入りするケースを何度か見ています。

もうひとつ、受講生からよく聞かれる質問が「どうやってstderrとstdoutの違いを見分けるか」というものです。手っ取り早い確認方法は、コマンドを2>/tmp/err_test.log付きで実行し、cat /tmp/err_test.logで内容を確認することです。何かエラーがあれば記録されています。

私がSE時代に3年間見落とし続けたエラーは、発見が遅れることで小さな問題が大きな障害に育っていました。最終的にデータ不整合を手作業で修正するのに丸1日かかりました。「エラーを記録する」という一行の違いが、障害対応の難易度を大きく変えることを、あの経験から身をもって学びました。

まとめ

stdoutとstderrの違いと、現場でのリダイレクト設計のポイントをまとめます。

やりたいこと リダイレクトの書き方
stdoutのみログに記録する command > file.log
stdoutとstderrを同じファイルに記録する command > file.log 2>&1
stdoutとstderrを別々に記録する command > out.log 2> err.log
追記モードでstdoutとstderrを記録する command >> file.log 2>&1
stderrを意図的に捨てる(確信がある場合のみ) command 2>/dev/null
スクリプト内でエラーをstderrに出力する echo "メッセージ" >&2
「エラーが出ていない」と「エラーが出ていないことを確認した」の違いを意識するだけで、あなたの障害対応力は格段に上がります。stderrを意識する習慣は、Linuxエンジニアとして現場で信頼されるための基礎のひとつです。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、
20年以上の現場経験から体系化した「Linux Master Pro Seminar」をご用意しています。
stderrの設計から本番環境でのcron運用まで、2日間のハンズオンで確実に身につけていただけます。

>> Linux Master Pro Seminarの詳細を見る

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。