Linux: パイプとフィルター — Unix哲学の真髄
Unix哲学の核心は「一つのことをうまくやる」です。パイプ|はこれらの小さなプログラムを連鎖させ — 一つの出力が次の入力になります。
📋 前提条件:まず以下を完了していること
- レッスン12:標準ストリームとリダイレクト
1. このレッスンで学ぶこと
- パイプ
|の仕組み - sortによる並び替えとuniqによる重複削除
- wcによる行/単語/文字数カウント
- teeによる二重出力
- 多段パイプラインの組み合わせの技法
2. 10GBのログファイルストーリー
(1) 悩み:ログが大きすぎて手動閲覧不可能
小明は10GBのNginxアクセスログからアクセス数トップ10のIPを見つける必要がありました。エディタで開くのは不可能(10GBは開けない)で、Pythonスクリプトを書くのは大げさでした。
(2) ワンライナーパイプラインの力
ボブがパイプでコマンドを組み合わせる方法を教えてくれました:
cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
ℹ️ 注意:
cat file | commandは一般的ですが、ここでのcatはファイルの内容をパイプに渡すだけ —command < fileまたはawk '{...}' fileを直接使う方が効率的で、プロセスを1つ節約できます。これは「UUOC」(Useless Use of Cat)と呼ばれ、動作はしますがパフォーマンスの最適化が可能です。
出力:
45231 192.168.1.100
38902 10.0.0.45
21045 192.168.1.200
(3) 得られるもの:1コマンドで複雑なタスク
小明はパイプラインの組み合わせでほぼあらゆるデータ抽出タスクを処理できることを発見しました — カウント、フィルタリング、並び替え、ランキング、重複削除。1つのコマンドがミニデータ処理パイプラインなのです。
3. 知識ポイント
(1) パイプの仕組み
command1 | command2 | command3
│ │ │
│ output │ output │
└──────────→│──────────→│──────────→ Final output
│ │
command1's stdout connects to command2's stdin
(2) よく使うフィルター
| コマンド | 用途 | よく使うオプション |
|---|---|---|
sort |
並び替え | -n数値、-r逆順、-k列指定、-u並び替え+重複削除 |
uniq |
重複削除(隣接行) | -cカウント、-d重複のみ表示、-u一意のみ表示 |
wc |
カウント | -l行数、-w単語数、-c文字数 |
head |
先頭表示 | -n 10最初の10行、-c 100最初の100文字 |
tail |
末尾表示 | -n 10最後の10行、-fリアルタイム追跡 |
tee |
ストリーム分割 | ファイルと画面の両方に出力 |
teeは配管の「T字継手」に由来 — パイプラインデータを2つに分け、一方は下流に渡し、もう一方をファイルに書き込みます。パイプラインのデバッグ時に中間にtee /tmp/debug.txtを挿入すると、最終出力に影響を与えずに中間結果をキャプチャできます。
| nl | 行番号付与 | -ba全行に番号付与 |
(3) ▶ サンプル:ファイル情報のカウント
# Count number of files
ls -la | wc -l
# Note: wc -l includes the "total" line, so actual file count is one less
# Count total lines in all .md files
cat *.md | wc -l
# Show disk usage of current directory
ls -lh | tail -n +2 | awk '{print $5, $9}'
出力:
TEXT15 342 4.0K config.yml 12M data.tar.gz
(4) ▶ サンプル:並び替えと重複削除
# Sort IP addresses (alphabetical vs numeric)
cat << EOF | sort
192.168.1.100
10.0.0.45
192.168.1.200
10.0.0.2
EOF
# sort -n for numeric sort (by IP segment)
cat << EOF | sort -t. -k1,1n -k2,2n -k3,3n -k4,4n
192.168.1.100
10.0.0.45
192.168.1.200
10.0.0.2
EOF
# Deduplicate and count
cat << EOF | sort | uniq -c | sort -rn
apple
banana
apple
orange
banana
apple
EOF
# 3 apple
# 2 banana
# 1 orange
出力:
TEXT10.0.0.2 10.0.0.45 192.168.1.100 192.168.1.200 3 apple 2 banana 1 orange
(5) ▶ サンプル:headとtail
# View first 5 lines
ls -la /etc | head -5
# View last 5 lines
ls -la /etc | tail -5
# Show lines 10-20
cat longfile.txt | head -20 | tail -11
# Follow a log in real-time (press Ctrl+C to exit)
tail -f /var/log/syslog
# View the last 10 log entries
tail -f -n 10 /var/log/syslog
出力:
TEXTtotal 72 drwxr-xr-x 2 root root 4096 Jul 7 10:00 bin drwxr-xr-x 3 root root 4096 Jul 7 10:00 lib drwxrwxrwt 15 root root 4096 Jul 7 10:30 tmp
# See results on screen and save to file simultaneously
ls -la | tee output.txt
# Append mode
echo "New content" | tee -a output.txt
# Save intermediate pipeline results
ps aux | tee processes.txt | grep nginx
# First save all processes with tee, then filter for nginx
出力:
TEXTtotal 72 drwxr-xr-x 2 root root 4096 Jul 7 10:00 bin ... New content root 1234 0.0 0.1 nginx: master
(6) ▶ サンプル:実践的パイプライン組み合わせ
# 5 largest files
ls -lhS | head -6
# Number of running shells
ps aux | grep bash | wc -l
# Partitions with disk usage over 80%
df -h | grep -v "tmpfs" | awk '{print $5, $6}' | sort -rn
# Find log files in /var/log modified within 7 days
find /var/log -name "*.log" -mtime -7 | sort
# Get external IP
curl -s ifconfig.me | tee my-ip.txt
出力:
TEXT-r--r--r-- 1 root root 12K config.pkt -rw-r--r-- 1 root root 8.9M data.tar.gz 3 203.0.113.42
(7) ▶ 総合例:ログ分析パイプラインチェーン
# Simulate an Nginx access log
cat << 'EOF' > /tmp/access.log
192.168.1.100 - - [07/Jul/2026:10:15:30 +0000] "GET /index.html HTTP/1.1" 200 1234
10.0.0.45 - - [07/Jul/2026:10:16:30 +0000] "GET /api/users HTTP/1.1" 200 5678
192.168.1.100 - - [07/Jul/2026:10:17:30 +0000] "POST /api/login HTTP/1.1" 401 234
10.0.0.45 - - [07/Jul/2026:10:18:30 +0000] "GET /api/users HTTP/1.1" 200 5678
192.168.1.200 - - [07/Jul/2026:10:19:30 +0000] "GET /index.html HTTP/1.1" 404 123
10.0.0.45 - - [07/Jul/2026:10:20:30 +0000] "GET /api/users HTTP/1.1" 200 5678
EOF
echo "=== Total Requests ==="
wc -l /tmp/access.log
echo ""
echo "=== Access Count per IP (Top 5) ==="
cat /tmp/access.log | awk '{print $1}' | sort | uniq -c | sort -rn
echo ""
echo "=== Count per HTTP Status Code ==="
cat /tmp/access.log | awk '{print $9}' | sort | uniq -c | sort -rn
echo ""
echo "=== Most Accessed URLs ==="
cat /tmp/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -5
echo ""
echo "=== 404 Error Requests ==="
grep " 404 " /tmp/access.log | awk '{print $1, $7}'
❓ よくある質問
Q: パイプラインは直列処理ですか、並列処理ですか? A: \1
Q: パイプとリダイレクトは同時に使えますか? A: はい。例えば
command > file 2>&1(リダイレクト)とcommand | grep "error"(パイプ)は組み合わせ可能:command 2>&1 | grep "error"。
Q: パイプライン途中でエラーが発生すると最終結果に影響しますか? A: はい。パイプライン内のコマンドが失敗すると、下流のコマンドは不完全なデータを受け取る可能性があります。スクリプト内で
set -o pipefailを使用すると、パイプライン内のいずれかのコマンドの失敗でパイプライン全体が失敗扱いになります。
Q:
tail -fの終了方法は? A:Ctrl + Cを押します。tail -fはファイルの新規内容を継続的に監視し、手動で中断するまで続きます。
Q: 大きなファイルのパイプライン処理でメモリ不足になりますか? A: \1
📖 まとめ
- パイプ
|はコマンドを連結:前のコマンドのstdout → 次のコマンドのstdin sortは並び替え、uniq -cは重複削除+カウント、wc -lは行数カウントhead -nは先頭N行、tail -fはリアルタイム追跡、teeは出力分割- 組み合わせ例:
cat log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10 - パイプラインは並列・ストリーミング処理 — 大きなファイルでもメモリ不足にならない
📝 練習問題
- 基本(難易度 ⭐):
ls -la | wc -lでファイル数をカウントし、/var/log/syslogまたはテストファイルから「error」を含む行を抽出してカウントする - 中級(難易度 ⭐⭐):
df -h | grep "^/" | sort -k5 -rnでディスク使用率が最も高いパーティションを見つけ、teeでps auxの出力をファイルに保存しながらターミナルにも表示する - 上級(難易度 ⭐⭐⭐):3つ以上のパイプラインコマンドを組み合わせてデータ処理 — システムのメモリ消費上位5プロセスを見つけ、メモリ使用量の降順で並べ、「PID コマンド メモリ%」の形式で出力する