シェルの基本 — パイプとリダイレクト
シェルのコマンドは、それぞれが小さな仕事しかしません。grep は絞り込むだけ、sort は並べ替えるだけ、wc は数えるだけです。
小さい道具で複雑な処理をこなせるのは、つなぐ仕組みがあるからです。パイプとリダイレクトがそれで、この 2 つを押さえれば大半の作業が組み立てられます。
この章で学ぶこと
- 標準入力・標準出力・標準エラー出力という 3 つの流れ
- パイプがコマンドどうしをどうつなぐか
- リダイレクトの
>と>>の違い - 標準エラー出力がパイプに乗らない理由と、その扱い方
ターミナルでコマンドを実行した経験があれば読めます。
3 つの流れ
どのコマンドも、既定で 3 つの入出力を持ちます。
| 名前 | 番号 | 役割 |
|---|---|---|
| 標準入力 (stdin) | 0 | 処理するデータが入ってくる口 |
| 標準出力 (stdout) | 1 | 処理結果を出す口 |
| 標準エラー出力 (stderr) | 2 | エラーや警告を出す口 |
出力が 2 つに分かれているのが要点です。**結果とエラーを別々の口から出すことで、結果だけを次の処理へ渡せます。**エラーは画面に出したまま、正常な結果だけをファイルへ保存する、といった使い分けができます。
番号 (0・1・2) はファイルディスクリプタと呼ばれ、リダイレクトで対象を指定するときに使います。
パイプ — 出力を次の入力へ
| は、左のコマンドの標準出力を右のコマンドの標準入力へつなぎます。
cat access.log | grep "POST" | wc -l
途中にファイルを作らずに、データが流れながら加工されます。何段でもつなげます。各段は別々のプロセスで同時に動いており、左が書いた分から右が読みます (プロセスとスレッド)。
パイプに乗るのは標準出力だけです。標準エラー出力は別の口なので、パイプを通らずそのまま画面に出ます。
# 存在しないファイルを指定した場合
cat missing.txt | grep buy
# → cat のエラーは画面に出る。grep には何も渡らない
これは意図された動きです。エラーメッセージが後続の処理へ流れ込むと、データとして扱われて誤動作します。
リダイレクト — ファイルへ向ける
リダイレクトは、入出力の相手をファイルに切り替えます。
| 記号 | 意味 |
|---|---|
> | 標準出力をファイルへ。ファイルの中身を空にしてから書く (上書き) |
>> | 標準出力をファイルへ。末尾に書き足す (追記) |
< | ファイルを標準入力へ |
2> | 標準エラー出力をファイルへ |
2>&1 | 標準エラー出力を標準出力と同じ先へ |
> と >> の違い
この差は実務での事故に直結します。
# > は毎回まっさらにする
grep buy log_a.txt > out.txt # out.txt = a の結果
grep buy log_b.txt > out.txt # out.txt = b の結果のみ。a の分は消えた
# >> は積み上がる
grep buy log_a.txt > out2.txt # 1 回目は > で作る (または >> でもよい)
grep buy log_b.txt >> out2.txt # out2.txt = a + b
日次のログを 1 つのファイルにためていく用途では >> を使います。> にすると、毎日その日のぶんだけになって過去が消えます。
> で既存ファイルを消してしまう事故は多いので、書き込み先が既存ファイルなら追記でよいかを毎回確かめる習慣が要ります。
標準エラー出力の扱い
command > out.txt # 結果だけファイルへ。エラーは画面
command 2> err.txt # エラーだけファイルへ。結果は画面
command > out.txt 2> err.txt # 別々のファイルへ
command > all.txt 2>&1 # 両方を同じファイルへ
command 2>/dev/null # エラーを捨てる
2>&1 の読み方は「ファイルディスクリプタ 2 の向き先を、1 の向き先と同じにする」です。& が付いているのは「1 という名前のファイル」ではなく「ファイルディスクリプタ 1」を指すためです。
順序に意味があります。
command > all.txt 2>&1 # 正しい。1 を all.txt へ向けてから、2 を 1 と同じ先へ
command 2>&1 > all.txt # 誤り。2 を画面へ向けてから、1 だけを all.txt へ
左から順に処理されるので、2>&1 は「その時点での 1 の向き先」にコピーされます。1 をファイルへ向ける前に書くと、エラーは画面に残ります。
grep — 行を絞り込む
パイプと組み合わせて最もよく使うコマンドです。
grep buy log.txt # ファイルから "buy" を含む行を抜き出す
cat log.txt | grep buy # 標準入力から抜き出す (同じ結果)
| オプション | 効果 |
|---|---|
-i | 大文字小文字を区別しない |
-v | 一致しない行を出す (反転) |
-n | 行番号を付ける |
-c | 一致した行数だけを出す |
-r | ディレクトリを再帰的に検索する |
-E | 拡張正規表現を使う |
grep はファイル名を引数に取れるので、cat file | grep pattern は grep pattern file と書けます。プロセスが 1 つ減るぶん後者のほうが効率的です。
ただしパイプの流れとして読みやすいという理由で cat から始める書き方も広く使われます。複数のコマンドをつないでいく途中で試行錯誤するときは、先頭が cat のほうが手を入れやすいためです。どちらが正しいというものではありません。
組み立ての例
# エラーログの発生回数を多い順に並べる
grep ERROR app.log | awk '{print $4}' | sort | uniq -c | sort -rn | head -10
# 特定の日付の POST リクエストだけを別ファイルへ追記する
grep "2026-08-17" access.log | grep POST >> post-2026-08.log
# 実行結果とエラーをまとめて記録しつつ、画面でも見る
./deploy.sh 2>&1 | tee deploy.log
tee は入力を「ファイルへ書きつつ標準出力へも流す」コマンドです。パイプの途中で内容を保存したいときに使います。スクリプトにまとめる段になると、環境変数とパスの扱いが次の関門になります。
よくある誤解
「> は追記」 — > は上書きです。追記は >> です。既存ファイルに > を使うと中身が消えます。
「パイプにはエラーも流れる」 — 流れるのは標準出力だけです。標準エラー出力を流したいなら 2>&1 で合流させます。
「2>&1 > file でエラーもファイルに入る」 — 入りません。順序が逆です。> file 2>&1 と書きます。
「cat file | grep x は間違い」 — 間違いではありません。grep x file のほうがプロセスは少なくて済みますが、パイプとして組み立てる書き方も一般的です。
確認問題
問 1. 次の日次バッチを 3 日間動かしました。4 日目の朝、daily.txt には何日ぶんの結果が入っていますか。/var/log/app.log は毎日 0 時にローテートされ、バッチは翌 0 時の直前に動くものとします。
#!/usr/bin/env bash
grep ERROR /var/log/app.log > daily.txt
答え: 1 日ぶん (3 日目の結果だけ)
> はファイルを開くときに中身を空にします。毎日実行すると前日までの内容が消え、最後に実行した日の結果だけが残ります。
3 日ぶんためたいなら >> にします。読み込むログが日ごとに入れ替わるので、追記していけば日ごとの結果が積み上がります。
grep ERROR /var/log/app.log >> daily.txt
**> と >> の選択は「そのファイルが最終的にどうなるか」で決めます。**日次で積み上げたいなら追記、毎回作り直したいなら上書きです。
ただし >> だけだと無限に増え続けるので、運用では次のような手当てを併せます。
| 手段 | 内容 |
|---|---|
| 期間でファイルを分ける | >> errors-$(date +%Y%m).log のように月次で切る |
| ログローテーション | logrotate などで一定のサイズや期間で切り替える |
問 2. スクリプトの出力とエラーの両方を 1 つのログファイルに残したいです。正しい書き方はどれですか。
A: ./run.sh > log.txt
B: ./run.sh 2> log.txt
C: ./run.sh > log.txt 2>&1
D: ./run.sh 2>&1 > log.txt
答え: C
- A — 標準出力だけがファイルへ。エラーは画面に残ります
- B — 標準エラー出力だけがファイルへ。結果は画面に残ります
- C — 正しい。まず 1 を
log.txtへ向け、次に 2 を「その時点の 1 の向き先」=log.txtへ向けます - D — **順序が逆です。**先に 2 を「その時点の 1 の向き先」= 画面へ向け、そのあとで 1 だけを
log.txtへ移します。結果はファイル、エラーは画面になります
リダイレクトは左から順に処理されることを押さえておくと、この違いを間違えません。
画面でも見たい場合は ./run.sh 2>&1 | tee log.txt を使います。
問 3. ./build.sh | grep -i warning を実行しても、画面に出ているはずの警告が 1 件も引っかかりません。なぜですか。
答え: 警告が標準エラー出力へ出ているためです。パイプに乗るのは標準出力だけです。
多くのツールは、処理結果を標準出力へ、警告やエラーを標準エラー出力へ分けて出します。画面には両方が混ざって表示されるので、見た目では区別が付きません。
grep へ渡したいなら、標準エラー出力を標準出力へ合流させます。
./build.sh 2>&1 | grep -i warning
逆にこの分離は意図された設計です。エラーメッセージが後続の処理へデータとして流れ込むと、集計や変換が誤動作します。「結果は次へ渡し、異常は人に見せる」という分担があるから、パイプで安全につなげます。
なお、標準エラー出力だけを絞り込みたい場合は、標準出力を捨ててから渡します。
./build.sh 2>&1 >/dev/null | grep -i warning
順序が独特です。パイプはコマンド自身のリダイレクトより先に組まれるので、2>&1 を評価する時点で 1 が向いているのはパイプです。そこへ 2 を向け、そのあとで 1 だけを /dev/null へ付け替えます。結果としてパイプへ流れるのは標準エラー出力だけになります。
これは bash の挙動です。zsh は既定で MULTIOS が有効なため、>/dev/null が 1 の向き先を置き換えず追加の出力先になり、標準出力もパイプへ流れてしまいます。どちらのシェルでも同じ結果にしたいなら、標準出力を先に捨てる形にします。
{ ./build.sh >/dev/null; } 2>&1 | grep -i warning
まとめ
- コマンドは標準入力・標準出力・標準エラー出力の 3 つの口を持ちます
- **パイプ
|は左の標準出力を右の標準入力へつなぎます。**標準エラー出力は流れません >は上書き、>>は追記です。既存ファイルへの>は中身を消します2>&1は「2 の向き先を、その時点の 1 の向き先に合わせる」。> file 2>&1の順序で書きますgrepはファイル名を直接取れます。catから始める書き方も組み立てやすさの点で一般的です
- ローカル開発環境 — このサイトを手元で動かすときのコマンド例
次に読む
- シェルスクリプトと環境変数 — スクリプトにまとめるときに踏む落とし穴