find・grep・awkの使い方入門 - 正規表現の基礎から
この記事でできるようになること
- find・grep・awk のどれを使うかを目的から選べる
- 正規表現のアンカー・量指定子・文字クラスを読み書きできる
- find の検索条件とアクションを組み合わせて安全に一括処理できる
前提知識(先に読むと理解しやすい記事)
基本的な Linux コマンドに慣れてきたら、次に覚えるのは find・grep・awk の 3 つです。この 3 つを押さえると、ファイル探し・ログ調査・集計を 1 行で片づけられるようになります。
この基礎編で扱うのは 3 点です。3 コマンドの役割と使い分け、3 コマンドすべてで効く 正規表現の基礎、そして find の検索機能 です。
この記事で身につくこと
- find・grep・awk のどれを使うかを、目的から選べるようになります。
- 正規表現のアンカー・量指定子・文字クラスを、読んで書けるようになります。
- find の検索条件とアクションを組み合わせて、一括処理を安全に実行できるようになります。
想定読者:ls / cd / cat などの基本コマンドを一度は触ったことがある人。
前提:find や grep で他人のファイルを対象にする場合は sudo が必要になります。パイプ(|)とリダイレクト(>)を使う例が出てきます。未習なら パイプとリダイレクトの基礎 を先に読んでください。
先に用語を整理する
初めて出てくる言葉を、ここで一度だけ定義します。
- 正規表現(regular expression)とは、「こういう形の文字列」をパターンで表す書き方です。「正規表現式」「regex」「regexp」とも呼びます。この記事では「正規表現」で統一します。
- メタ文字とは、
^$.*のように、文字そのものではなく特別な意味を持つ記号です。「特殊文字」とも呼びます。 - アンカーとは、行頭・行末・単語境界といった「位置」を指すメタ文字です。文字そのものにはマッチしません。
- 量指定子とは、直前のパターンが何回繰り返されるかを指定する記号です。「繰り返し指定」「quantifier」とも呼びます。
- 文字クラスとは、
[0-9]のように「この範囲のどれか 1 文字」を表す書き方です。 - エスケープとは、メタ文字の前に
\を付けて「記号そのもの」として扱わせることです。 - 標準エラー出力(stderr)とは、エラーメッセージが流れ出る出口です。
2>/dev/nullは、その出口を捨てるという指定です。
3つのコマンドの概要と使い分け
結論: find は場所探し、grep は中身探し、awk はデータ加工が得意。目的に合わせて使い分けるのが効率化の第一歩。
まず、各コマンドの特徴と用途を理解する。適切なコマンドを選ぶこと が効率的な作業の第一歩。
find:ファイル・ディレクトリ検索
- 名前でファイルを検索
- サイズ・日付での絞り込み
- 権限・所有者での検索
- 見つけたファイルに対する一括処理
得意分野は 「どこにあるかわからないファイルを探す」。
find /home -name "*.txt" -size +1M
grep:テキスト内容検索
- ファイル内のテキスト検索
- 正規表現を使った高度な検索
- ログファイルの解析
- 設定ファイルの確認
得意分野は 「ファイルの中身から特定の文字列を探す」。
grep -r "ERROR" /var/log/
-r は再帰(recursive)の指定で、指定ディレクトリの下を階層ごと辿る。/var/log/ 配下は所有者が root のファイルが多いため、読めない場合は sudo grep -r "ERROR" /var/log/ を使う。
awk:テキスト処理・データ加工
- 列データの抽出・計算
- CSVファイルの処理
- ログファイルの集計
- フォーマット変換
得意分野は 「データを加工・集計・変換する」。
awk '{sum+=$3} END {print sum}' sales.csvawk は入力を 1 行ずつ読み、空白で区切った各項目を フィールド(列)として扱う。$1 が 1 列目、$3 が 3 列目、NF が列の総数を指す。上の例は 3 列目を合計し、全行を読み終えた時点(END)で結果を表示している。
判断フロー
| 状況 | 使うコマンド |
|---|---|
| ファイルの場所がわからない | find |
| ファイルの中身から文字列を探したい | grep |
| データを加工・集計したい | awk |
正規表現マスタークラス
結論: 正規表現はアンカー・量指定子・文字クラスの組み合わせ。BRE / ERE / PCRE の違いを押さえれば3コマンドで応用できる。
正規表現(Regular Expression)は、find、grep、awk の真の力を引き出すための必須スキル。基礎から実務で即座に使えるパターンまで習得する。
正規表現の種類
正規表現には方言が 3 つあります。同じパターンでもツールによって解釈が変わるため、最初にこの区別を押さえます。
| 種類 | 略称 | 対応ツール | 特徴 |
|---|---|---|---|
| 基本正規表現 | BRE | grep, sed, vi | メタ文字をエスケープ必要 |
| 拡張正規表現 | ERE | egrep, grep -E, awk | より直感的な記法 |
| Perl互換正規表現 | PCRE | grep -P, perl | 最も高機能(先読み・後読み) |
grep は指定なしだと BRE です。+ や ? を素直に書きたい場合は -E を付けて ERE に切り替えます。
位置指定(アンカー)
# 行頭がERRORで始まる行 grep "^ERROR" logfile.txt # .logで終わる行 grep "\.log$" filelist.txt # portという単語(report等は除外) grep -E "\bport\b" config.txt
文字クラス
# 192.168.1.x のIPアドレス grep "192\.168\.1\." access.log # 時刻形式(HH:MM) grep "[0-9][0-9]:[0-9][0-9]" log.txt # 英数字以外の文字を含む行 grep "[^a-zA-Z0-9]" data.txt
量指定子(Quantifiers)
# 0回以上の繰り返し(errorとfailedが同じ行にある)
grep "error.*failed" log.txt
# 1回以上の繰り返し(ERE)
grep -E "[0-9]+" data.txt
# 0回または1回(httpまたはhttps)
grep -E "https?" urls.txt
# n回以上m回以下(2〜4桁の数字)
grep -E "[0-9]{2,4}" data.txt高度なパターンマッチング
グループ化と OR 条件:
# 複数のキーワードをOR条件で検索 grep -E "(error|warning|critical)" log.txt
先読み・後読みアサーション(PCRE):
# 「円」の前の数字だけを抽出 grep -P "\d+(?=円)" price.txt # test.txt以外のtestを含むファイル grep -P "test(?!\.txt)" filelist.txt # $記号の後の数字を抽出 grep -P "(?<=\$)\d+" invoice.txt
実務で使える正規表現パターン集
ログ解析:
# IPアドレス(IPv4)
grep -E "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" access.log
# 日時パターン(Apache形式)
grep -E "\[[0-9]{2}/[A-Z][a-z]{2}/[0-9]{4}:[0-9]{2}:[0-9]{2}:[0-9]{2} [+-][0-9]{4}\]" access.log
# HTTPステータスコード集計
grep -E "\" [1-5][0-9]{2} " access.log | awk '{print $9}' | sort | uniq -c
# エラーレベル抽出
grep -E "\b(DEBUG|INFO|WARN|ERROR|FATAL|CRITICAL)\b" app.logステータスコードの集計で $9 を指定しているのは、Apache の combined format ではステータスが 9 列目に来るため。User-Agent が空白で分割されて列数が行ごとに変わるので、$(NF-1) のような末尾からの指定は使えない。
データ検証:
# メールアドレス(簡易版)
grep -E "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b" contacts.txt
# URL(http/https)
grep -E "https?://[^[:space:]\"']+" webdata.txt
# 電話番号(日本)
grep -E "(0[0-9]{1,4}-?[0-9]{1,4}-?[0-9]{4})" contacts.txtURL の例で [^\s...] ではなく [^[:space:]...] を使っているのは、ブラケット式([ ])の内側では \s が空白文字クラスとして解釈されないため。[^\s"'] は「バックスラッシュ・s・"・' 以外」の意味になり、URL に s が現れた時点で打ち切られる。ブラケット式の内側では POSIX 文字クラス [:space:] を使う。
コード解析:
# 関数定義(JavaScript/Python)
grep -E "^(function|def)\s+[a-zA-Z_][a-zA-Z0-9_]*\s*\(" *.js *.py
# 変数宣言(JavaScript)
grep -E "^(var|let|const)\s+[a-zA-Z_][a-zA-Z0-9_]*" *.js
# TODO/FIXMEコメント
grep -E "(TODO|FIXME|XXX|HACK|NOTE):" -n *.py正規表現のデバッグ
複雑な正規表現は 段階的に構築 すること。
# Step 1: 数字を含む行 grep "[0-9]" test.txt # Step 2: 1つ以上の数字 grep "[0-9]\+" test.txt # Step 3: 数字のみの行 grep "^[0-9]\+$" test.txt
-o オプションで部分マッチを確認できる:
echo "test123abc456" | grep -o "[0-9]\+"
123 456
BREとEREでのエスケープの違い
同じ「192.168.1. のあとに 1 文字以上」を書き分けると次のようになる。
- BRE: 量指定子
+をエスケープする →grep "192\.168\.1\..\+" access.log - ERE: 量指定子
+はそのまま書ける →grep -E "192\.168\.1\..+" access.log
どちらも \. がリテラルのドット、. が任意の 1 文字。BRE でエスケープが必要なのは量指定子だけで、. はエスケープしない。grep "192\.168\.1\.\+" と書くと \+ が直前の \. に係るため、「ドットが 1 個以上続く」という別の意味になる。
パフォーマンス最適化
正規表現を高速化する3つのコツ
- アンカーを活用:
grep "^error" huge.logのほうがgrep "error" huge.logより速い - 不要な
.*を除去:grep "error" log.txtで十分(.*error.*は遅い) - 固定文字列は
-Fオプション:grep -F "exact_string" file.txtで正規表現エンジンを使わず高速化
findコマンド:ファイル検索の極意
結論: find は検索場所・検索条件・アクションの3要素で構成。名前やサイズ、日付で絞り込み一括処理まで実行できる。
find は ファイルシステムを縦横無尽に検索 できる強力なコマンド。
基本構文
find [検索場所] [検索条件] [アクション]
検索場所で検索条件に合うファイルを見つけ、アクションを実行する。
名前による検索
# 拡張子が.txtのファイルを検索 find /home -name "*.txt" # 「config」で始まるファイル find . -name "config*" # 大文字小文字を区別せずに.logファイルを検索 find /var -iname "*.LOG"
ファイル種別による検索
-type は探す対象の種類を絞る。f が通常ファイル、d がディレクトリ、l がシンボリックリンク(別のファイルを指す「ショートカット」のこと。「シンボリックリンク」「symlink」「ソフトリンク」はすべて同じものを指す)。
# 通常ファイルのみ find /home -type f # 「log」で始まるディレクトリ find /var -type d -name "log*" # シンボリックリンク find /tmp -type l
サイズによる検索
-size には注意点が 1 つある。指定した単位に切り上げてから比較するという仕様だ。-size -1k は「切り上げて 1KB 未満」つまり 0 単位のファイルを指すため、0 バイトのファイルだけに一致する。500 バイトのファイルは 1KB に切り上げられるため一致しない。
# 100MB より大きい find /var -size +100M # 0 バイトのファイル(切り上げの結果 0 単位になるもの) find /home -size -1k # 1024 バイト未満を厳密に指定する(c = バイト単位) find /home -size -1024c # 1GB より大きく 10GB 未満(切り上げ後の単位で比較される) find . -size +1G -size -10G
バイト単位で厳密に絞り込みたい場合は、k / M / G ではなく c(バイト)を使う。切り上げが起きないため、意図した境界で絞り込める。
日付・時刻による検索
時刻の条件は 3 種類ある。mtime(modification time)は中身が変更された時刻、atime(access time)は読み込まれた時刻、ctime(change time)は権限や所有者などの属性が変わった時刻を指す。数値の単位は日で、-7 は「7 日以内」、+30 は「30 日より前」を意味する。
# 過去7日以内に変更されたファイル(mtime) find /home -mtime -7 # 30日以上前に変更されたファイル find /var/log -mtime +30 # 1日以上アクセスされていないファイル(atime) find /tmp -atime +1 # reference.txtより新しいファイル find /home -newer reference.txt
権限・所有者による検索
-perm は権限(パーミッション)で絞り込む。setuid ビットとは、実行したユーザーではなくファイル所有者の権限で動く指定のことで、悪用されると権限昇格の踏み台になる。そのため棚卸しの対象になる。
# 権限が755のファイル find /home -perm 755 # setuidビットが設定されたファイル(セキュリティチェック用) find / -perm -4000 2>/dev/null # www-dataユーザーが所有するファイル find /var -user www-data # developersグループが所有するファイル find /home -group developers
実行アクション
find の真の力は、見つけたファイルに対して自動で処理を実行できること。ただし、この機能は同時に最も事故が起きやすい部分でもある。
先に読む:-delete と -exec は取り消せない
-delete はファイルをゴミ箱に入れずに消す。-exec chmod は該当ファイル全部の権限を書き換える。失敗するとどうなるかは次の 2 点。
- 検索条件が広すぎた場合、意図していないファイルまで削除・変更される。
rmと同じく、削除したファイルは元に戻せない(バックアップがなければ復旧不能)。
安全に試す方法は、必ず 2 段階に分けること。
- 先に
-printだけを付けて実行し、対象一覧を目で確認する。 - 一覧が想定どおりなら、
-printを-deleteや-execに差し替える。
# Step 1: 対象を確認するだけ(何も変更しない) find /tmp -name "*.tmp" -print # Step 2: 一覧が想定どおりなら削除する find /tmp -name "*.tmp" -delete
-delete は必ず条件のあとに書く。find は指定順に評価するため、find /tmp -delete -name "*.tmp" と書くと -name を評価する前に削除が走り、/tmp 配下がすべて消える。
練習は /tmp 配下に自分で作った作業用ディレクトリで行う。/・/etc・/var を対象にした練習はしない。
ファイル削除:
# 一時ファイルを一括削除 find /tmp -name "*.tmp" -delete # 30日以上古いログファイルを削除 find /var/log -name "*.log" -mtime +30 -delete
-delete は find 自身が削除する仕組みで、-exec rm {} \; よりも速く安全に動く。ファイル名に空白や改行が含まれていても正しく扱える。ただし -delete は -depth(深い階層から先に処理する)を暗黙に有効化するため、-prune による枝刈りとは併用できない。
権限変更:
# PHPファイルの権限を644に変更
find /var/www -name "*.php" -exec chmod 644 {} \;
# ディレクトリの権限を755に変更
find /home -type d -exec chmod 755 {} \;{} は見つかったファイル名に置き換わるプレースホルダー、\; は 1 件ずつ実行するという区切り。\; を + に変えると複数件をまとめて渡すため高速になる。
権限は記録しておかないと戻せない
ls -l の -rw-r--r-- という表示からは chmod に渡す数値を機械的に復元できない。戻せる状態を作るには、実行前に数値パーミッションをファイルへ保存する。
# 実行前: 数値パーミッションをファイルに保存する
find /var/www -name "*.php" -exec stat -c '%a %n' {} + > ~/perm-backup.txt
# 戻すとき: 保存した値を 1 行ずつ書き戻す
while read -r mode path; do chmod "$mode" "$path"; done < ~/perm-backup.txt情報収集:
# txtファイルの詳細情報を表示
find /home -name "*.txt" -exec ls -lh {} \;
# 100MB以上のファイルのサイズを表示
find /var -size +100M -exec du -h {} \;findコマンドのベストプラクティス
検索範囲を限定
ルートディレクトリ(/)から検索すると、ディスク全体を走査するため時間がかかる。稼働中のサーバではディスク負荷の原因にもなる。できるだけ具体的なディレクトリを指定する。
- 良い例:
find /var/log -name "*.log" - 悪い例:
find / -name "*.log"
途中で止めたい場合は Ctrl+C を押す。検索は読み取りのみなので、途中で止めてもファイルは壊れない。
権限エラーを回避
アクセス権のないディレクトリのエラーメッセージを 2>/dev/null で非表示にする。
find / -name "*.txt" 2>/dev/null
効率的な条件組み合わせ
複数条件を組み合わせて精密に検索する。
# 1MB以上、7日以内のログファイル find /home -name "*.log" -size +1M -mtime -7
トラブルシューティング
結論: 詰まる原因はほぼ 4 つ。権限不足・引用符忘れ・正規表現の方言違い・検索範囲の広さ。
症状: Permission denied が大量に出る
原因: 自分に読み取り権限のないディレクトリを走査している。
確認:
find /var -name "*.log"
対処: エラーだけを捨てるか、sudo を付ける。
find /var -name "*.log" 2>/dev/null # エラーを捨てる sudo find /var -name "*.log" # 権限を借りて読む
症状: find . -name *.txt が想定と違う結果になる
原因: 引用符がないため、*.txt をシェルが先に展開してしまう。
確認:
find . -name *.txt
対処: 検索パターンは必ず引用符で囲む。
find . -name "*.txt"
症状: grep "[0-9]+" が何もマッチしない
原因: grep は指定なしだと BRE で動くため、+ が「文字としての +」と解釈される。
確認:
echo "abc123" | grep "[0-9]+"
対処: -E で ERE に切り替えるか、+ をエスケープする。
echo "abc123" | grep -E "[0-9]+" # ERE echo "abc123" | grep "[0-9]\+" # BRE でエスケープ
症状: grep を実行するとプロンプトが返らない
原因: 検索対象のファイル名を書き忘れたため、grep が標準入力からの入力を待っている。
確認: 何も表示されず、キー入力を受け付ける状態になっている。
対処: Ctrl+C で中断し、ファイル名を指定して再実行する。パイプで受け取る場合は入力側のコマンドが必要。
grep -E "ERROR" app.log # ファイルを指定する tail -100 app.log | grep "ERROR" # パイプで渡す
症状: find が返ってこない
原因: 検索範囲が広すぎる(/ 全域など)。
確認: Ctrl+C で中断し、指定したパスを見直す。
対処: 対象ディレクトリを具体的に絞る。深さを制限する -maxdepth も有効。
find /home/user -maxdepth 3 -name "*.log"
作業完了チェックリスト
- [ ] 目的(場所探し / 中身探し / 加工)から使うコマンドを選べた
- [ ] 正規表現を書くとき、BRE と ERE のどちらで動いているか意識できた
- [ ]
-deleteや-execの前に-printで対象を確認した - [ ] 検索範囲を
/ではなく具体的なディレクトリに絞った