こんにちは、かつコーチです。
「2>&1って何のためにあるの」「エラーメッセージだけを別のログファイルに残したい」。
シェルスクリプトを書いていると、必ずと言っていいほど突き当たるのが入出力の仕組みです。
この記事では、コマンドの入出力の土台となる標準入力・標準出力・標準エラー出力を、初中級者向けに整理して解説します。
パイプ・リダイレクト(10-pipe-redirect-tsunageru)を理解するための前提知識にもなります。
標準入力・標準出力・標準エラー出力とは?
コマンドの「会話の入り口と出口」
コマンドやスクリプトは、外部とデータをやり取りするための3つの通り道をあらかじめ持っています。
- 標準入力(stdin):コマンドがデータを受け取る入り口
- 標準出力(stdout):コマンドの通常の実行結果を出す出口
- 標準エラー出力(stderr):コマンドのエラーメッセージを出す出口
会話にたとえると、標準入力は相手の話を聞く「耳」、標準出力は普通に話す「口」、標準エラー出力は「これはまずいですよ」と警告だけを別枠で伝える出口です。
出力を「通常の返答」と「警告・エラー」の2系統に分けているのがポイントで、これによって成功した結果とエラーメッセージを別々に扱えるようになっています。
なぜ標準出力と標準エラー出力が分かれているのか
もし出力が1系統しかなければ、正常な結果とエラーメッセージが同じ場所に混ざってしまい、ログファイルに保存したときに区別がつきません。
標準出力と標準エラー出力を分けておくことで、「正常な結果だけをファイルに保存し、エラーは別のログに残す」といった使い分けが可能になります。
シェルスクリプトを本格的に自動化・運用するうえで欠かせない仕組みです。
基本の書き方
ファイルディスクリプタという番号で管理されている
標準入力・標準出力・標準エラー出力には、それぞれ番号(ファイルディスクリプタ)が割り振られています。
| 番号 | 名称 | 役割 |
|---|---|---|
| 0 | 標準入力(stdin) | データを受け取る入り口 |
| 1 | 標準出力(stdout) | 通常の実行結果の出口 |
| 2 | 標準エラー出力(stderr) | エラーメッセージの出口 |
リダイレクト記号の前にこの番号を付けることで、どの出入り口を操作するかを指定できます。
標準出力とエラー出力を分けて書き込む
#!/bin/bash
# 標準出力をresult.logに、標準エラー出力をerror.logに書き込む
ls /path/to/dir 1> result.log 2> error.log
存在しないディレクトリを指定した場合、エラーメッセージはerror.logにだけ記録され、result.logは空のままになります。
標準入力からデータを受け取る
readコマンドは、標準入力からデータを1行読み込みます。
#!/bin/bash
echo "お名前を入力してください:"
read name
echo "こんにちは、${name}さん"
キーボードからの入力だけでなく、リダイレクトやパイプで渡されたデータも同じ標準入力として受け取れます。
#!/bin/bash
# ファイルの中身を標準入力として渡す
./greet.sh < name.txt
よくあるつまずきポイント・エラー対処
2>&1の書く位置でエラーログが正しく取れない
コマンドの実行結果を1つのログファイルにまとめたくて、次のように書いたことがあります。
❌Before(つまずいた実例)
#!/bin/bash
# すべての出力をapp.logにまとめたいつもりだった
./deploy.sh 2>&1 > app.log
実行してみると、標準エラー出力だけがターミナルにそのまま表示され、app.logには標準出力しか記録されていませんでした。
✅After(解決方法)
原因は、リダイレクトが左から右へ順番に処理されることを理解していなかったためです。2>&1を先に書くと、その時点でまだ標準出力先が変わっていない(ターミナルのまま)ため、標準エラー出力はターミナルに向いた状態で固定されてしまいます。> app.logを先に書き、標準出力の向き先を確定させたあとで2>&1を書くように順序を入れ替えたところ、意図通りすべてがapp.logにまとまりました。
#!/bin/bash
# 先に標準出力をapp.logへ、その後エラー出力を標準出力と同じ場所へ
./deploy.sh > app.log 2>&1
「2>&1はコピーした時点の向き先を引き継ぐ」という仕組みを知らないと、順番を変えるだけで結果が変わる理由がわからず戸惑うポイントです。
応用・一歩先の使い方
エラーだけを捨てる・エラーだけを見る
運用スクリプトでは、エラーだけを非表示にしたり、逆にエラーだけを確認したいケースがよくあります。
#!/bin/bash
# 標準エラー出力だけを捨てて、通常の出力だけ表示する
find / -name "*.conf" 2> /dev/null
# 標準エラー出力だけをターミナルに残し、標準出力だけをファイルに保存する
./build.sh > build.log
/dev/nullは「送った内容をすべて消費して何も残さない場所」で、不要な出力の捨て場所としてよく使われます。
ヒアドキュメントで複数行の標準入力を渡す
<<を使うヒアドキュメントを使うと、複数行のテキストをまとめて標準入力として渡せます。
#!/bin/bash
cat << EOF
これは1行目です。
これは2行目です。
EOF
設定ファイルの生成や、対話式コマンドへの複数行入力を自動化する場面で活用できます。
まとめ
この記事のポイント
- 標準入力(0番)はデータの入り口、標準出力(1番)は通常結果の出口、標準エラー出力(2番)はエラーの出口
1>と2>で出力先を出口ごとに分けて指定できる2>&1はリダイレクトの左から右への処理順が重要で、書く位置によって結果が変わる/dev/nullは出力を捨てるための場所として使える
次に読むべき記事
入出力の仕組みがわかったら、次はコマンドをつなげて活用する「パイプとリダイレクトでコマンドをつなげる」を読んでみてください。
タグ: Shell, 中級者向け, 引数・入出力