1-4. テキスト処理ツール - grep, awk, sed, sort, uniq, wc
所要時間: 25-50分(がっつりなら2セッション分) ゴール: ログファイルから必要な行を抜き出し、整形・集計してレポートを作れる コミット内容: 今日の操作履歴と作ったワンライナーを
~/log/linux_day04.logに保存
この章が終わるとできること
grep -i / -n / -v / -r / -Eを使い分けて、ログから必要な行だけ抜けるawk '{print $N}'で N 列目を取り出し、$5 == "ERROR"で条件抽出できるsed 's/old/new/g'で置換、sed -n '5,10p'で範囲抽出ができるsort | uniq -c | sort -rnの 「集計三種の神器」 ワンライナーが書けるwc -l / -w / -cで行・単語・バイト数を即カウントできる
Day 1-3 とのつながり
- Day 1 の
cat / less / tailは「ファイル全体を見る」道具だった - Day 4 はその先 ─「必要な行・列・パターンだけ取り出す」道具を覚える
- Day 3 で
Permission deniedのエラーログを見ても「読めない」と困った人へ、今日からはgrepで原因行だけ拾える
これができると何が嬉しいか
- 500MB のログから「ユーザー X の 500 エラーだけ」を秒で抽出できる
- 「アクセス上位 IP トップ10」みたいなレポートが 1行のコマンド で出せる
- Day 5(パイプ)以降、これらの道具を
|で繋ぐ快感が始まる
大前提: なぜ「テキスト処理」がバックエンドの中核なのか
Unix哲学の有名なフレーズ: “Everything is a file. Every file is text.”(すべてはファイル、すべてのファイルはテキスト)。
実際バックエンドの仕事の大半は テキストを読み書きする こと:
- ログ: アクセスログ、エラーログ、アプリログ → すべてテキスト
- 設定ファイル: nginx, systemd, Dockerfile, YAML, JSON → すべてテキスト
- API のレスポンス: JSON, XML → テキスト
- データ移行: CSV, TSV のインポート/エクスポート → テキスト
- メトリクス: Prometheus形式, ログ形式の数値 → テキスト
「テキストを早く・正確に料理する」スキルは、本番サーバーで障害対応する時の差を決定的に分ける。GUI のログビューワーが無い環境で、500MBのログから「特定ユーザーのエラーだけ」を抜き出して時系列に並べる、みたいな作業を秒で終わらせる人と、1時間かかる人の違いは、ここで学ぶ道具を使えるかどうか。
今日は5つの道具を覚える: grep(検索)/ awk(フィールド処理)/ sed(置換)/ sort(並べ替え)/ uniq(重複排除・カウント)/ wc(カウント)。
セッション①: 閲覧の発展 + grep(25-30分)
0. 録画スタート&作業データ準備
mkdir -p ~/log ~/learn/linux/day04
cd ~/learn/linux/day04
script ~/log/linux_day04.log
# 練習用のダミーログを作成
# ↓ cat > file <<'EOF' ... EOF は「ヒアドキュメント」と呼ばれる構文。Linux 1-5 で扱う。
# ここではコピペで OK、目的は access.log というテキストファイルを作ること。
cat > access.log <<'EOF'
2026-05-14 10:00:01 192.168.1.10 GET /api/users 200
2026-05-14 10:00:02 192.168.1.11 POST /api/login 200
2026-05-14 10:00:03 192.168.1.10 GET /api/users 200
2026-05-14 10:00:05 10.0.0.5 GET /api/products 404
2026-05-14 10:00:08 192.168.1.10 GET /api/orders 500
2026-05-14 10:00:10 10.0.0.5 GET /api/products 404
2026-05-14 10:00:12 192.168.1.12 POST /api/login 401
2026-05-14 10:00:15 192.168.1.10 GET /api/users 200
2026-05-14 10:00:20 10.0.0.5 GET /api/products 404
2026-05-14 10:00:25 192.168.1.13 DELETE /api/orders/1 500
2026-05-14 10:00:30 192.168.1.99 GET /health 200
2026-05-14 10:00:30 192.168.1.99 GET /health 200
2026-05-14 10:00:30 192.168.1.99 GET /health 200
2026-05-14 10:00:31 192.168.1.99 GET /health 200
2026-05-14 10:00:31 192.168.1.99 GET /health 200
EOF
cat access.log1. 閲覧コマンドの発展(cat / less / head / tail 再訪)
Day 1 で覚えた cat / less / head / tail を 「ログ調査でガチに使う」レベルまで掘り下げる。本番でログを追う時には「ファイル全部を読む」より「先頭/末尾/特定行範囲だけを素早く見る」のが圧倒的に多い。ここでは特に重要な以下の使い方を押さえる。
tail -f: ファイルに追記される行をリアルタイムで追う(本番ログ監視の最頻出)head -n N/tail -n N: 行数を明示的に指定して先頭/末尾を見るhead | tailの組み合わせ: 「途中の行範囲だけ抜き出す」ワザcat -n: 行番号付きで全表示(短いログの目視確認に)
これらは grep / awk / sed と組み合わせる前提のコマンドなので、ここで再復習して「長いファイルから必要な範囲を一瞬で切り出す」感覚を身体に染み込ませる。
# 連番でファイル名を出力
cat -n access.log
# 末尾10行(デフォルト)
tail access.log
# 末尾を「現在進行形で」追跡
tail -f access.log # Ctrl+C で止める
# 先頭5行
head -n 5 access.log
# 5行目から10行目まで
head -n 10 access.log | tail -n 6
tail -fは本番調査の最頻出コマンドログにリアルタイムで出力される新規行を逐次表示する。アプリの動作確認・障害調査の必須スキル。
使い所:
- デプロイ直後に
tail -f /var/log/nginx/error.logでエラー出てないか監視- APIを叩いた瞬間に何が起きているかリアルタイムで観察
- 別ターミナルで動かしながら開発する
-F(大文字)の違い: ファイルがローテーション(古いログがリネームされて新しいログファイルに切り替わる)された時、-fは古いファイルを追い続けるが、-Fは新しいファイルに切り替えて追跡する。長時間ログ監視するなら-F。
headとtailを組み合わせて「途中の行」を取り出す# 50行目から60行目まで head -n 60 file.log | tail -n 11 # 末尾の100行のうち、先頭10行(=末尾100-91行目) tail -n 100 file.log | head -n 10「行範囲指定」がしたい時はこの形が定番。
sedでも書けるが、こちらの方が直感的。
2. grep - テキスト検索の基本
grep は 「ファイルの中からパターンにマッチする行だけを抜き出して表示するコマンド。Unix最古参のテキスト処理ツールで、名前は g/re/p(global / regular expression / print)の略。バックエンド作業で最も叩く回数が多いコマンドの1つで、これが使えるかどうかが「ログ調査スピードの差」を直撃する。
実務での頻出シーン:
- エラー調査:
grep "ERROR" /var/log/app.logで大量ログからエラー行だけ抜く - 特定ユーザー/IPの行動追跡:
grep "192.168.1.10" access.log - コードベース全体から TODO を発掘:
grep -rn "TODO" src/ - 設定ファイルから特定のキー行だけ取り出す:
grep -E "^(host|port)" config.yml
文字列マッチだけでなく正規表現にも対応するので、「数字3桁で始まる行」「行末が .log の行」のような複雑な条件も書ける。さらにパイプ | で awk sort uniq と繋げば、ログ集計ワンライナーの主役になる。
# 単純検索
grep "200" access.log
# 大文字小文字無視
grep -i "delete" access.log
# 行番号も表示
grep -n "404" access.log
# 一致しない行(反転)
grep -v "200" access.log
# 複数ファイルを再帰検索
grep -r "TODO" ~/learn/linux/
# マッチした件数だけカウント
grep -c "500" access.log
# 拡張正規表現で OR 検索
grep -E "404|500" access.log
grepの本質「ファイルの中から、パターンにマッチする行を抜き出す」コマンド。名前は g/re/p(global / regular expression / print)の略。Unix の最古参テキストツールの一つ。
基本構造:
grep <パターン> <ファイル>パターンは単なる文字列でも、正規表現でも書ける。
grep の主要オプション
オプション 意味 用途 -iignore case 大文字小文字を区別しない -nline number 行番号を表示(後で sed -iで編集する時の下準備に便利)-vinvert マッチしない行を出す -rrecursive ディレクトリ配下を再帰検索 -ccount マッチ数だけ表示(行は出さない) -lfiles with match マッチしたファイル名だけ表示 -Eextended regex 拡張正規表現(` -A 3after マッチ行の後3行も表示 -B 3before マッチ行の前3行も表示 -C 3context 前後3行表示 --colorカラー マッチ箇所を色付け(多くの環境でデフォルト有効)
grep の実務ユースケース
- エラー調査:
grep "ERROR" /var/log/app.logでエラー行だけ抽出- 特定IPのアクセス:
grep "192.168.1.10" access.log- 404を除いた他のエラーだけ見たい:
grep -E "5[0-9]{2}" access.log(5xx エラー)- TODOコメントを全部探す:
grep -rn "TODO" src/- 設定ファイルから特定キーを探す:
grep -E "^(host|port)" config.yml
正規表現の最低限
grepは拡張正規表現
-Eで以下が使える:
^: 行の先頭$: 行の末尾.: 任意の1文字*: 直前の文字の0回以上の繰り返し+: 1回以上の繰り返し(-E が必要)?: 0回または1回[abc]: aかbかc[0-9]: 数字|: OR(-E が必要)(...): グループ化例:
^ERROR→ 行頭が ERROR の行200$→ 行末が 200 の行[0-9]+→ 1文字以上の数字(GET|POST)→ GET または POST
grep の落とし穴
- シェルが先にワイルドカードを展開してしまう:
grep *.log file.logのように書くとシェルが*.logを展開しておかしくなる。パターンは必ずクォート:grep "*.log" file.log-rで巨大ディレクトリを舐めると遅い:node_modulesなどを--exclude-dirで除外:grep -r "TODO" . --exclude-dir=node_modules- 基本正規表現と拡張正規表現の罠:
grep単体だと|や+がそのままの文字として扱われる。OR や+を使うなら-Eを付けるかgrep -P(Perl正規表現)を使う- macOS の grep は GNU grep と挙動が違うことがある: 困ったら
brew install grepで GNU 版を入れる選択肢も
3. wc - 行数・単語数・文字数のカウント
wc (word count) は ファイル(または stdin)の行数・単語数・バイト数・文字数を数えるシンプルなコマンド。地味だが「集計の最後の仕上げ」として超頻出。
実務での使い方:
- 「このログ何行ある?」:
wc -l access.log - 「grep でヒットした件数は?」:
grep "ERROR" app.log | wc -l(またはgrep -cでも可) - 「ディレクトリ内に
.tsファイル何個ある?」:find . -name "*.ts" | wc -l - 「今日のアクセス数」:
grep "$(date +%Y-%m-%d)" access.log | wc -l($(...)は「中のコマンドを実行して、その出力を文字列としてここに埋め込む」というシェル記法=コマンド置換。$(date +%Y-%m-%d)は2026-05-21のような今日の日付に置き換わる。詳細は Linux 2-2 で扱う)
オプションは -l(行)/ -w(単語)/ -c(バイト)/ -m(文字)の4つを覚えれば実務OK。マルチバイト文字(日本語等)を扱う時は -c(バイト)と -m(文字)で値が変わるので意識する。
# 全部出す(行数、単語数、バイト数、ファイル名)
wc access.log
# 行数だけ
wc -l access.log
# 単語数だけ
wc -w access.log
# パイプ経由
grep "200" access.log | wc -l # 200を含む行が何行あるか
wc(word count) の本質ファイルの 行数 / 単語数 / バイト数 を数える。地味だが超頻出。
オプション:
-l(lines): 行数-w(words): 単語数(空白区切り)-c(bytes): バイト数-m(chars): 文字数(マルチバイト考慮)実務ユースケース:
- 「このログ何行あるんだ?」:
wc -l access.log- 「grep でヒットした件数は?」:
grep "ERROR" app.log | wc -l(またはgrep -c "ERROR")- 「ディレクトリ内の .ts ファイル数」:
find . -name "*.ts" | wc -l
wc -lの地味な罠末尾に改行が無いファイルは「最後の行」が数えられない。
echo -n "abc" > file && wc -l fileで0が返る。本番ログは普通改行で終わるので問題ないが、自作テキストでハマることあり。
セッション②: awk / sed / sort / uniq(25-30分)
4. sort - 並べ替え
sort は テキストを「行単位で並べ替える」コマンド。ログを時系列順に整える、集計の前処理として並べる、サイズや回数の順位を作る、など使い所は無限にある。
実務で頻出する組み合わせ:
sort | uniq -c | sort -rn: 出現頻度ランキング(ログ集計の鉄板パターン、次節で扱う)du -h | sort -h: ディスク使用量を人間が読める単位で大きい順- 複数サーバーのログをマージしてから時系列に並べる:
cat *.log | sort
最大の落とし穴は 「デフォルトは辞書順(文字列比較)」 という点。2 < 10 は数値だが、文字列としては "10" < "2"(先頭の 1 と 2 の比較で 1 が小さい)になる。数値ソートしたい時は -n を必ず付ける。これを忘れて「ソート結果がおかしい」と悩むのは新人あるある。
# アルファベット順
sort access.log
# 数値順(重要)
echo -e "10\n2\n100\n3" | sort # 辞書順: 10, 100, 2, 3
echo -e "10\n2\n100\n3" | sort -n # 数値順: 2, 3, 10, 100
# 逆順
sort -r access.log
# 重複削除(ソートしたものに対して)
sort -u access.log
# 特定カラムでソート(スペース区切り、3列目=IPで並べる)
sort -k 3 access.log
# 数値で逆順、特定カラム
sort -k 6 -n -r access.log # 6列目(ステータスコード)の数値で大きい順sort vs sort -n の違い
ざっくり言うと
sort は デフォルトで「辞書順」。数字を渡しても 文字列として 比較する。これを知らずに「2 < 100 のはずが逆になる」で詰まる新人あるある。数値で並べたいなら -n 必須。
まず普通のパターン
数字を縦に並べてソートしてみる:
echo -e "10\n2\n100\n3" | sort期待:
2
3
10
100
実際:
10
100
2
3
「は?」となる。なぜこうなる?
なぜ辞書順になるのか
sort は 1文字目から順に文字コードで比較する。
"10" ← 1文字目は '1'
"100" ← 1文字目は '1'
"2" ← 1文字目は '2'
"3" ← 1文字目は '3'
→ 1文字目の ‘1’ < ‘2’ < ‘3’ で並ぶ。 → ‘1’ で始まる行同士は2文字目で比較、結果 “10” < “100”。
sort から見たら全部「文字列」。数字としては扱われない。
図で見る
sort(デフォルト):
"10" → '1' '0'
"2" → '2'
→ '1' < '2' なので "10" が先
→ 人間からすると不自然
sort -n:
"10" → 10
"2" → 2
→ 数値として比較
→ 2 < 10 で人間直感通り
解決策: -n を付ける
echo -e "10\n2\n100\n3" | sort -n結果:
2
3
10
100
イメージ:
-n = numeric。「この入力は数値だと思って比較してね」とソートに教える。
対比表
sort | sort -n | |
|---|---|---|
| 比較方法 | 辞書順(文字列) | 数値順 |
10 と 2 の順 | 10 が先(‘1’ < ‘2’) | 2 が先(2 < 10) |
| 用途 | 名前、ID、ログ行 | カウント、サイズ、件数 |
| 数字を渡すと | バグる | 正しく動く |
よくある「ハマる」シーン
# 「アクセス数の多い順」を出したい
awk '{print $3}' access.log | sort | uniq -c | sort -r
↑
ここに -n が無いと
100 が 99 より前に来る事故正解:
awk '{print $3}' access.log | sort | uniq -c | sort -rn-rn = reverse + numeric。「集計の最後の sort には常に -n」 と覚える。
関連オプションも整理
| オプション | 意味 |
|---|---|
-n | 数値として比較(最重要) |
-r | 逆順 |
-u | 重複削除 |
-k <N> | N列目をキーに |
-t <delim> | 区切り文字指定 |
-h | 1K, 2M のような単位付き数値も理解 |
一番覚えやすい説明
sortは文字列ソート(数字も文字として扱う)- 数字でソートしたいなら必ず
-n - 集計ワンライナーの最後の
sortには-rnを付けるのが定石
sort の実務ユースケース
- ログを時系列に並べる: 複数サーバーから集めたログをマージしてソート
du -h | sort -hで人間が読めるサイズの大きい順- 集計の前処理:
uniqと組み合わせる時は必ず先にsort(後述)
5. uniq - 重複の処理
uniq は 「連続する重複行を1つにまとめる、または重複回数をカウントする」コマンド。地味な見た目だが、sort と組み合わせると 「同じ値が何回出てきたか」の集計が一発でできる、ログ解析の主力ツール。
最大の注意点は 「隣接行しか見ない」 という性質。離れた場所にある同じ値は別物として扱われるので、必ず sort で隣接させてから uniq に渡すのが鉄則。「sort | uniq -c で集計、もう一回 sort -rn で多い順に並べる」のパターンが、ログ集計の半分以上を占めると言っても過言ではない。
実務での使い所:
- アクセス数ランキング:
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head - エラー種類別の発生件数:
grep "ERROR" app.log | awk '{print $5}' | sort | uniq -c - 重複ファイル名のチェック:
find . -name "*.tmp" | sort | uniq -d
-c(カウント)、-d(重複行のみ)、-u(一意な行のみ)の3つのオプションを使い分けるだけで、ほとんどの集計に対応できる。
# 隣接する重複を1つに(必ず sort と組み合わせる)
sort access.log | uniq
# 重複行を「何回出現したか」カウント付きで
sort access.log | uniq -c
# 重複している行だけ表示
sort access.log | uniq -d
# 一度しか出ない行だけ表示
sort access.log | uniq -uなぜ uniq は sort とセットで使うのか?
ざっくり言うと
uniq は 「隣り合った重複」しか見ない。離れた場所にある同じ値は別物扱い。だから先に sort で並べて、同じものを隣り合わせてから uniq に渡す必要がある。
まず普通のパターン
こういうログがあるとする:
192.168.1.10
192.168.1.20
192.168.1.10
192.168.1.30
192.168.1.10
「重複を消したい」と思って uniq だけ使うと:
cat ips.log | uniq結果:
192.168.1.10
192.168.1.20
192.168.1.10 ← 残ってる
192.168.1.30
192.168.1.10 ← 残ってる
「重複消えてないじゃん!」となる。なぜ?
uniq の仕様: 「隣り合った」重複だけ消す
uniq は1行ずつ流れてくる入力を見て、直前の行と同じなら捨てる という動き方をする。
入力:
192.168.1.10 ← 出す
192.168.1.20 ← 直前と違う、出す
192.168.1.10 ← 直前(.20)と違う、出す! ← 重複なのに残る
192.168.1.30 ← 直前と違う、出す
192.168.1.10 ← 直前(.30)と違う、出す! ← 重複なのに残る
つまり、離れた場所にある同じ値は気付けない。
図で見る: なぜ sort が必要か
入力ファイル(バラバラ):
a
b
a ← この a は b の隣にいるので「別物」扱い
c
a ← この a も「別物」扱い
uniq だけ通す:
a, b, a, c, a ← 全部残る
先に sort:
a
a ← 隣同士に並ぶ
a ← 隣同士に並ぶ
b
c
sort | uniq:
a, b, c ← 正しく重複削除
解決策: sort | uniq のパイプ
cat ips.log | sort | uniqこれで初めて「ファイル全体の重複削除」になる。
イメージ:
sort= 同じものを隣同士に並べるuniq= 隣り合った重複を消す- → セットで初めて「全体の重複削除」が成立
集計の鉄板パターン
「どのIPが何回出たか」を多い順に出す:
cat ips.log | sort | uniq -c | sort -rn各ステップを図で:
入力(バラバラ):
192.168.1.10
192.168.1.20
192.168.1.10
192.168.1.30
192.168.1.10
↓ sort
ソート済み:
192.168.1.10
192.168.1.10
192.168.1.10
192.168.1.20
192.168.1.30
↓ uniq -c(カウント付きで重複削除)
カウント:
3 192.168.1.10
1 192.168.1.20
1 192.168.1.30
↓ sort -rn(数値で逆順)
ランキング:
3 192.168.1.10
1 192.168.1.20
1 192.168.1.30
対比表: uniq の主要オプション
| オプション | 意味 |
|---|---|
-c | 出現回数を頭に付ける(集計の主役) |
-d | 重複している行だけ表示 |
-u | 重複してない行だけ表示 |
-i | 大文字小文字無視 |
一番覚えやすい説明
uniqは「隣の行」しか見ない- だから
sort | uniqがワンセット - ログ集計は
sort | uniq -c | sort -rnの三段重ねがテンプレ
uniq オプション
-c(count): 各行の出現回数をカウント-d(duplicated): 重複している行だけ表示-u(unique): 重複してない行だけ表示-i(ignore case): 大文字小文字を区別しない
6. awk - フィールド処理の最強ツール
awk は テキストを「行」と「列(フィールド)」に分解して処理する小さなプログラミング言語。1行を空白区切りで切って $1, $2, ... で各列にアクセスし、条件マッチ + アクションで集計・抽出・整形を一気にやれる。「ログから3列目だけ取り出したい」「特定カラムが○○の行だけ抜きたい」などで頻出。
# 各行の3列目(IP)を表示
awk '{print $3}' access.log
# 1列目(日付)と6列目(ステータスコード)を表示
awk '{print $1, $6}' access.log
# 条件付き: 500エラーの行だけ抽出
awk '$6 == 500' access.log
# 条件付き + 列指定: 500エラーのIPだけ
awk '$6 == 500 {print $3}' access.log
# 区切り文字を変える(CSV処理)
echo "a,b,c,d" | awk -F',' '{print $3}' # c が出る
# 集計(合計)
awk '{sum += $6} END {print sum}' access.log # ステータスコードの合計(無意味だが構文例)
# パターン + アクション
awk '/ERROR/ {print $1, $2}' app.log
awkの本質「テキストを表として扱う」プログラミング言語。1行を「1つのレコード」、空白区切りの各部分を「フィールド(列)」として扱い、
$1,$2, … でアクセスできる。awk の動作モデル:
awk 'パターン { アクション }' ファイル
- パターン: マッチ条件(省略可、省略時は全行が対象)
- アクション: マッチした行に対して何をするか(省略時は
{print}=その行を出す)特殊変数:
$0: 行全体$1, $2, ...: 各フィールドNF: フィールド数(その行に何列あるか)NR: レコード番号(今が何行目か)FS: 入力区切り文字(デフォルトは空白)
awk の主要パターン
やりたいこと 書き方 全部の3列目を取り出す awk '{print $3}'5列目が “ERROR” の行 awk '$5 == "ERROR"'3列目が数値で 100 以上 awk '$3 >= 100'カンマ区切り(CSV) awk -F',' '{...}'各行の最後の列 awk '{print $NF}'5行目だけ表示 awk 'NR == 5'「合計」を計算 awk '{sum += $3} END {print sum}'行ごとに通し番号 awk '{print NR, $0}'
awk の実務ユースケース
- アクセスログのIPだけ抽出:
awk '{print $1}' access.log(nginxのデフォルトログ形式の場合)- CSVから特定カラムだけ抜く:
awk -F',' '{print $2, $5}' data.csv- エラーログの時刻だけリスト化:
awk '/ERROR/ {print $1}' app.log- 平均値・合計の計算:
awk '{sum+=$3; count++} END {print sum/count}' metrics.txt
awk の落とし穴
- シングルクォート必須: シェル変数展開を防ぐため、awk のプログラムは必ず
'...'で囲む==と=の区別:==が比較、=が代入。$3 == "ERROR"を$3 = "ERROR"と書くと「代入」になり全行が真と判定される- 数値か文字列か:
awk '$3 == 100'で動くこともあるが、フィールドが文字列として扱われる場合がある。明示的に$3+0 == 100で数値化するテクも- macOSのawkはBSD版でGNU版と挙動が違う: 複雑な処理を書くなら
brew install gawkで GNU awk を入れてgawkとして使う
7. sed - 置換の基本
sed (stream editor) は テキストを1行ずつ流しながら「置換・削除・抽出」を行うコマンド。s/置換前/置換後/ の文法で文字列を一括変換するのが最も典型的な使い方。設定ファイルの一括修正やログの整形でよく使う。
# 単純置換(最初の1個だけ)
echo "hello world" | sed 's/world/Linux/'
# 行内の全ての一致を置換(g フラグ)
echo "a a a" | sed 's/a/b/g'
# ファイル内容を置換して標準出力(元ファイルは変わらない)
sed 's/200/OK/' access.log
# ファイルを直接書き換え(-i)
sed -i 's/200/OK/g' access.log # Linux
sed -i '' 's/200/OK/g' access.log # macOS(''必須!)
# 特定行を削除
sed '3d' access.log # 3行目を削除
sed '/404/d' access.log # 404を含む行を削除
# 行範囲を抽出
sed -n '5,10p' access.log # 5-10行目だけ表示
sed(stream editor) の本質「ストリーム(流れてくるテキスト)に対して、行単位で編集する」ツール。awkがフィールド処理に強いのに対し、sedは置換と削除に特化。
最頻出は置換:
s/置換前/置換後/(sは substitute)。sed 's/old/new/' # 各行の最初の old だけ new に sed 's/old/new/g' # 各行の全ての old を new に(g = global) sed 's/old/new/2' # 各行の2番目の old を new に sed 's|old|new|g' # 区切り文字を | に変更(パスを置換する時に便利)
sed の実務ユースケース
- 設定ファイルの一括変更:
sed -i 's/localhost/prod.example.com/g' config.yml- ログから余計な情報を削る:
sed 's/\[INFO\] //g' app.log- 改行コードの変換: Windows形式(CRLF)を Unix 形式(LF)に:
sed -i 's/\r$//' file.txt- 特定行の削除:
sed -i '/DEBUG/d' app.logで DEBUG ログ行を削除
sed -iの地獄
- macOS の
-iは空文字列を要求する:sed -i '' 's/.../.../' file。Linux と書き方が違う-iは「直接書き換え」なので戻せない: バックアップなしで打って後悔するパターン多発。sed -i.bak 's/.../.../' fileでバックアップ付き編集に- 置換対象に
/が含まれる時: 区切り文字を変えるsed 's|/old/path|/new/path|g'- 正規表現の特殊文字をエスケープし忘れる:
.*[]などはエスケープが必要
7.5. grep / awk / sed の使い分け
grep vs awk vs sed どう使い分ける?
ざっくり言うと
3つとも「テキストを処理する」コマンドだが、得意分野が違う。
grep= 行を抜き出す(フィルター)awk= 列を扱う(フィールド処理 + 集計)sed= 文字を置き換える(置換・削除)
まず普通のパターン
同じログから「500エラーの IP」だけ取り出したい。どのコマンドで書く?
2026-05-14 10:00:08 192.168.1.10 GET /api/orders 500
2026-05-14 10:00:25 192.168.1.13 DELETE /api/orders/1 500
2026-05-14 10:00:30 192.168.1.99 GET /health 200
それぞれで書いてみる
grep でやると
grep "500$" access.log→ 「500 で終わる行」を抜き出す。でも IP だけにはできない。grep の出番はここまで。
awk でやると
awk '$6 == 500 {print $3}' access.log→ 「6列目が 500 の行から、3列目(IP)だけ表示」。条件 + 列指定を1コマンドで。
sed でやると
sed -n '/500$/s/.*\(192[^ ]*\).*/\1/p' access.log→ 書ける、けど 読めない。sed は置換に特化したコマンドなので、こういう抽出には向かない。
図で見る: 3つの守備範囲
1行のテキスト: 2026-05-14 10:00:08 192.168.1.10 GET /api/orders 500
─────────────────── ──────────── ─── ────────── ───
↑
列に分解できるのが awk
行全体を見て該当行を抜くのが grep
文字パターンを別の文字に置き換えるのが sed
何をしたいかでコマンドを選ぶ
| やりたいこと | 第一候補 | 例 |
|---|---|---|
| 特定の文字列を含む行が欲しい | grep | grep "ERROR" app.log |
| 特定の列だけ取り出したい | awk | awk '{print $3}' file |
| 条件で行を絞って列を取りたい | awk | awk '$6 == 500 {print $3}' |
| 文字を別の文字に置換 | sed | sed 's/old/new/g' file |
| 特定行を削除 | sed | sed '/DEBUG/d' file |
| 行数を数える | wc -l | grep "ERROR" | wc -l |
| 集計(カウント) | sort | uniq -c | (ワンライナーで) |
対比表
| grep | awk | sed | |
|---|---|---|---|
| 主な役割 | 行のフィルタリング | フィールド処理 | 置換・削除 |
| 単位 | 行 | 行 + 列 | 行 + 文字 |
| 集計できる? | できない | できる | できない |
| 置換できる? | できない | できる(やや手間) | 得意 |
| 学習コスト | 低 | 中 | 中 |
| 1コマンドの守備範囲 | 狭い | 広い | 中 |
イメージ
grep= 網(マッチする行だけ通す)awk= 電卓 + テーブル分解(列を操って計算もできる)sed= 置換マシン(文字を別の文字に交換)
3つを連携させる
実務では パイプで繋いで連携 させるのが普通:
# 「エラー行を抜く → IP列だけ取る → カウント」
grep "ERROR" app.log | awk '{print $3}' | sort | uniq -c | sort -rn
↑ ↑ ↑
grep で絞る awk で列抽出 sort + uniq で集計それぞれが「1つのことだけうまくやる」という Unix 哲学そのもの。
一番覚えやすい説明
- grep = 行を抜く(検索)
- awk = 列を扱う(抽出 + 集計)
- sed = 文字を変える(置換)
- 迷ったら grep でフィルタ → awk で列処理 → sort/uniq で集計 が定番
8. パイプで組み合わせる(次章の予告)
# 1. 全アクセスログから
# 2. 500エラーの行を抜き出し
# 3. IP(3列目)を取り出し
# 4. 並べ替えて
# 5. 重複をカウントして
# 6. 多い順にソート
grep "500" access.log | awk '{print $3}' | sort | uniq -c | sort -rnパイプ
|の威力ここで紹介した道具は どれも「1つの仕事だけ」をする(Unix哲学)。代わりに、パイプ
|で連結して任意の複雑な処理を組み立てられる。パイプの仕組みそのものは Day5: パイプとリダイレクト で詳しくやる。今日は「
コマンドA | コマンドBで A の出力が B に流れ込む」とだけ覚えておく。
9. 練習課題
# 1. access.log から GET リクエストだけ抜き出す
grep "GET" access.log
# 2. ステータスコード別の件数を集計
awk '{print $6}' access.log | sort | uniq -c | sort -rn
# 3. 一番アクセスが多いIPトップ3
awk '{print $3}' access.log | sort | uniq -c | sort -rn | head -n 3
# 4. 404エラーを起こしたパスだけ抽出
awk '$6 == 404 {print $5}' access.log
# 5. ログのステータスコードを「成功/失敗」に置換(成功=2xx, 失敗=4xx/5xx)
sed -E 's/ 2[0-9]{2}$/ SUCCESS/; s/ [45][0-9]{2}$/ FAILURE/' access.log
# 6. 「総アクセス数」と「ユニークIP数」を出す
# ↓ `$(コマンド)` はコマンド置換: 中のコマンドを実行して、その出力結果をここに埋め込む(詳細は Linux 2-2)。
# `< access.log` は「ファイルの中身をコマンドの標準入力に流し込む」リダイレクト(詳細は Linux 1-5)。
# ここではコピペで動かしてみるだけでOK、目的は echo で集計結果を1行にまとめて表示すること。
echo "総アクセス: $(wc -l < access.log)"
echo "ユニークIP: $(awk '{print $3}' access.log | sort -u | wc -l)"締め: 振り返り(10分)
1. セッション録画を終了
exit2. 今日の発見
このノートに追記:
- 一番「これ便利」だったコマンド:
grepはかなり便利、使い所が多いと感じた
- awk と sed、それぞれどういう時に使い分けると理解した:
awkは抽出、集計に強い sedは削除、集計に強い
- 「ワンライナーで集計できる」のヤバさを感じた瞬間:
- 明日やりたいこと:
チェックリスト
-
grep -i / -n / -v / -r / -Eを実際に試した -
awk '{print $1}'で特定列を取り出した -
awk '$6 == 500'のような条件抽出ができた -
sed 's/old/new/g'で置換した -
sort -nとsort(辞書順)の違いを観察した -
sort | uniq -c | sort -rnの「集計ワンライナー」を体感した -
wc -lで行数カウントした
詰まった時のチートシート
| やりたいこと | コマンド |
|---|---|
| パターンを含む行を出す | grep "pattern" file |
| 大文字小文字無視 | grep -i "pattern" file |
| マッチしない行 | grep -v "pattern" file |
| 件数カウント | grep -c "pattern" file |
| ディレクトリを再帰検索 | grep -r "pattern" dir/ |
| OR検索 | grep -E "a|b" file |
| 特定列を取り出す | awk '{print $3}' file |
| 条件付き抽出 | awk '$5 == "ERROR"' file |
| CSV処理 | awk -F',' '{print $2}' file.csv |
| 文字列置換 | sed 's/old/new/g' file |
| 行削除 | sed '/pattern/d' file |
| 行範囲抽出 | sed -n '5,10p' file |
| ファイル直接編集 | sed -i.bak 's/x/y/g' file |
| 並べ替え(辞書順) | sort file |
| 数値で並べ替え | sort -n file |
| 重複カウント | sort file | uniq -c |
| 出現頻度ランキング | sort | uniq -c | sort -rn |
| 行数カウント | wc -l file |
「実務OK」基準
- grep で「あるパターンが何回出るか」を即答できる
- awk で「N列目」を抜き出す書き方を覚えている
- sort | uniq -c | sort -rn の集計パターンを使える
- sed で「置換」と「削除」の基本構文が書ける
- これらをパイプで繋いで、ログ集計のワンライナーが書ける
ここまで来れば「ログ集計でハマらない人」。明日 Day5 で パイプ・リダイレクト・xargs を体系的にやって、ここで学んだ道具を本格的に繋げる力を付ける。
アンチパターン / 初心者やらかし事例
NG 1: grep で正規表現の特殊文字をエスケープし忘れる
grep "192.168.1.1" access.log # `.` が「任意の1文字」と解釈されて誤マッチ→ 対策: 固定文字列なら grep -F、または grep "192\.168\.1\.1" でエスケープ。
NG 2: sed -i を MacOS と Linux で同じ書き方をして失敗
sed -i 's/a/b/g' file.txt # macOS では「`-i` の後ろに拡張子必須」でエラー→ 対策: 移植性重視なら sed -i.bak 's/a/b/g' file.txt と書く(macOS でも GNU sed でも動く)。.bak がバックアップを残す。
NG 3: 巨大ログを cat | grep で読む(UUOC)
cat access.log | grep ERROR # `cat` 不要→ cat を介すと 無駄なプロセスとパイプが1個増える(数百MBで体感差)。grep ERROR access.log で十分。Useless Use of Cat (UUOC) と呼ばれる古典的アンチパターン。
NG 4: sort -u と sort | uniq -c を混同
sort access.log | uniq # 重複は消える、カウントは出ない
sort access.log | uniq -c # カウント付き
sort -u access.log # `uniq` と同じ結果、カウントは出ない→ 集計したいなら 必ず uniq -c。-u はあくまで「重複削除」だけ。
自己評価チェックリスト
-
grep -i / -v / -Eの意味をすべて答えられる -
awk '{print $1}'とawk -F',' '{print $1}'の違い(区切り文字)を説明できる -
sed 's/old/new/g'のgを外したら何が起きるか答えられる -
sort | uniq -c | sort -rnの 3ステップそれぞれの役割 を口頭で説明できる - 「アクセス上位 IP トップ10」のワンライナーをホワイトボードで書ける
次のレッスン: Day 5 - パイプとリダイレクト
今日学んだ grep awk sed sort uniq wc を、|(パイプ)で繋ぐ世界 に進む。
実は Day 4 で何度か出てきた ... | ... という書き方。これを体系的に理解すると、「1行のコマンドで集計・整形・保存まで完結」する Unix の真骨頂が見える。