32blogby StudioMitsu

find実践ガイド:ファイル検索を自在に操る

findコマンドで名前・サイズ・日時・権限からファイルを検索する方法を実例で解説。-execの使い分け、-pruneで高速化、条件組み合わせ、実務パターン集。

18 min read
目次

find はディレクトリツリーを再帰的にたどり、条件に合うファイルやディレクトリをすべて返す。名前パターン、タイムスタンプ、サイズ、パーミッション——あらゆる条件で検索でき、見つけたファイルにその場でアクションを実行できる。

要は find /path -name "*.log" -type f/path 以下の .log ファイルを全部探す。-exec で結果に即アクション、-mtime で日時絞り込み、-size でサイズ絞り、-prune でディレクトリスキップ。条件は -and-or-not で組み合わせ自在。

findが今も最強な理由

fdが速いのは知ってる。単純な検索なら20倍速い。でも find にしかできないことがある:

  • どこにでもある。 Linux、Dockerコンテナ、CIランナー、どんな環境にも入っている。インストール不要
  • -exec が組み込み。 パイプでxargsに渡さなくても、見つけたファイルに直接コマンドを実行できる
  • 複雑な条件検索。 AND、OR、NOT、カッコでのグループ化——ファイルシステム用のクエリ言語
  • タイムスタンプの精密制御。 分単位で更新日時・アクセス日時・ステータス変更日時を指定
  • パーミッション検索。 SUID、ワールドライタブル、特定ユーザー所有のファイルを探す

僕は普段のファイル検索には fd を使うけど、ビルドサーバーの掃除やプロダクション環境のパーミッション監査では find が必須。32blogのデプロイ時に .next のキャッシュが4GBまで膨らんでいたとき、サイズと更新日時の両方で絞り込めるのは find だけだった。

基本: 名前・タイプ・パスで探す

名前で検索

bash
# すべてのTypeScriptファイルを検索
find . -name "*.ts"

# 大文字小文字を区別しない検索
find . -iname "*.readme"

-name はシェルのglobパターン(正規表現ではない)。パターンはファイル名全体に一致する必要がある——"*.ts"app.ts にマッチするが app.tsx にはマッチしない。

タイプで絞り込む

bash
# ファイルのみ(ディレクトリを除外)
find . -name "*.log" -type f

# ディレクトリのみ
find /etc -type d -name "nginx"

# シンボリックリンクのみ
find /usr/local/bin -type l

よく使う -type の値: f(ファイル)、d(ディレクトリ)、l(シンボリックリンク)。

検索開始パスを指定

bash
# 複数ディレクトリから検索
find /var/log /tmp -name "*.log" -type f

# ルートから検索(遅い場合は -maxdepth を検討)
find / -name "nginx.conf" 2>/dev/null

更新日時で絞り込む: -mtime, -newer

時間ベースの絞り込みが find の真骨頂。ファイルには3つのタイムスタンプがある:

オプションタイムスタンプ意味
-mtimemtime内容の更新日時
-atimeatime最終アクセス(読み取り)日時
-ctimectimeメタデータ変更日時(権限、所有者)

+/- 記法を理解する

-mtime の後の数字は「24時間単位で何日前か」:

bash
# 30日より前に更新されたファイル(古いファイル)
find /var/log -name "*.log" -mtime +30

# 24時間以内に更新されたファイル(新しいファイル)
find . -name "*.mdx" -mtime -1

# ちょうど7日前に更新されたファイル(168〜192時間前)
find . -mtime 7

分単位で指定する

-mtime の代わりに -mmin を使えば分単位で指定できる:

bash
# 15分以内に変更されたファイル
find . -mmin -15

# 60分以上アクセスされていないファイル
find . -amin +60

基準ファイルより新しいファイルを探す

bash
# deploy.logの更新日時より新しいファイル
find . -newer deploy.log

# タイムスタンプマーカーを作成し、それより新しいファイルを検索
touch -t 202603200000 /tmp/marker
find /var/log -newer /tmp/marker

サイズとパーミッションで絞る

サイズで絞る

bash
# 100MBより大きいファイル
find / -type f -size +100M

# 1KB未満のファイル
find . -type f -size -1k

# 空ファイル
find . -type f -empty

# 空ディレクトリ
find . -type d -empty

サイズの単位: c(バイト)、k(キロバイト)、M(メガバイト)、G(ギガバイト)。

パーミッションで絞る

bash
# 全ユーザー書き込み可能なファイル(セキュリティ監査)
find / -type f -perm -o=w 2>/dev/null

# SUIDビット付きバイナリ(セキュリティ監査)
find / -type f -perm -4000 2>/dev/null

# パーミッションが正確に644のファイル
find . -type f -perm 644

# オーナーが実行権限を持つファイル
find . -type f -perm -u=x

-perm の記法:

  • -perm 644 — 完全一致(644でなければマッチしない)
  • -perm -644 — 指定ビットがすべてセットされている(他のビットがあってもOK)
  • -perm /644 — 指定ビットのいずれかがセットされている

所有者で絞る

bash
# ユーザー "deploy" が所有するファイル
find /var/www -user deploy

# グループ "www-data" が所有するファイル
find /var/www -group www-data

# 所有者がいないファイル(ユーザーが削除された孤立ファイル)
find / -nouser 2>/dev/null

見つけたファイルに即アクション: -exec, -delete, -print0

ファイルを見つけるだけで終わりではない。find は結果に直接アクションを実行できる。

-exec と ;(1ファイルずつ実行)

bash
# すべてのシェルスクリプトに実行権限を付与
find . -name "*.sh" -exec chmod +x {} \;

# 各.confファイルの詳細情報を表示
find /etc -name "*.conf" -exec ls -la {} \;

{} は現在のファイル名に置き換わる。\; はコマンドの終端(バックスラッシュでシェルからセミコロンをエスケープ)。

-exec と +(バッチ実行)

できるだけ多くのファイル名を1回のコマンドにまとめる——圧倒的に速い:

bash
# すべてのPythonファイルの行数を一括カウント
find . -name "*.py" -exec wc -l {} +

# 全ファイルの所有者を一括変更
find /var/www -type f -exec chown deploy:www-data {} +

+xargsと同じように引数をまとめる。対象コマンドが複数引数を受け取れるならこちらを使う。

-delete(削除の組み込みアクション)

bash
# .tmpファイルをすべて削除
find /tmp -name "*.tmp" -type f -delete

# 空ディレクトリを削除
find ./build -type d -empty -delete

-print0(安全なパイプ出力)

xargsにパイプする場合、スペースや特殊文字を含むファイル名に対応するため -print0 を使う:

bash
# 安全なパイプライン: nullバイト区切り
find . -name "*.log" -print0 | xargs -0 gzip

# -print0なしだと "my log.txt" が "my" と "log.txt" に分かれて壊れる

検索の深さを制御する: -maxdepth, -prune

深さを制限する

bash
# カレントディレクトリのみ(再帰しない)
find . -maxdepth 1 -name "*.txt"

# カレント + 1階層下まで
find . -maxdepth 2 -type f -name "*.json"

# 最初の階層をスキップ(サブディレクトリからのみ検索)
find . -mindepth 2 -name "*.ts"

ディレクトリをスキップする(-prune)

-prune はディレクトリツリーまるごとスキップする——パフォーマンスに直結:

bash
# .tsファイルを探すが node_modules はスキップ
find . -path "*/node_modules" -prune -o -name "*.ts" -print

# 複数ディレクトリをスキップ
find . \( -path "./.git" -o -path "./node_modules" -o -path "./.next" \) -prune -o -name "*.ts" -print

パターンは -path "スキップしたいディレクトリ" -prune -o <実際の条件> -print-o は「または」——findがディレクトリをpruneするか、マッチングを続行するか。

32blogのようなNext.jsプロジェクトで .nextnode_modules.git をスキップすると、検索が12秒から1秒未満に短縮される。

条件の組み合わせ: AND, OR, NOT

デフォルトでは複数条件はAND結合:

bash
# .logファイルかつ10MBより大きい(暗黙のAND)
find /var/log -name "*.log" -size +10M

# 明示的なAND(結果は同じ)
find /var/log -name "*.log" -and -size +10M

OR条件

bash
# .jpg または .png ファイル
find . -name "*.jpg" -o -name "*.png"

# アクションを付ける場合はグループ化(カッコ)が必要
find . \( -name "*.jpg" -o -name "*.png" \) -exec ls -la {} +

NOT条件

bash
# .git以外のすべてのファイル
find . -not -path "*/.git/*"

# 短縮形: -not の代わりに !
find . ! -name "*.tmp"

複雑な条件の組み合わせ

bash
# TypeScriptファイルで50KB超、今日更新されたもの(テスト除外)
find src/ \( -name "*.ts" -o -name "*.tsx" \) \
  -size +50k -mtime 0 \
  -not -path "*/__tests__/*" \
  -not -name "*.test.*"

実務で使うパターン集

パターン1: 古いビルド成果物の掃除

bash
# 7日以上前の.nextキャッシュを削除
find .next/cache -type f -mtime +7 -delete

# ~/projects以下の全プロジェクトからnode_modulesを削除
find ~/projects -maxdepth 3 -name "node_modules" -type d -prune -exec rm -rf {} +

パターン2: ディスクを食っている大きなファイルを探す

bash
# システム全体で50MB超のファイル上位20件
find / -type f -size +50M -exec ls -lhS {} + 2>/dev/null | head -20

# /var内で90日以上アクセスされていない100MB超のファイル
find /var -type f -size +100M -atime +90 2>/dev/null

パターン3: セキュリティ監査

bash
# SUID/SGIDビット付きバイナリを検出
find / -type f \( -perm -4000 -o -perm -2000 \) -exec ls -la {} + 2>/dev/null

# 全ユーザー書き込み可能ディレクトリ(セキュリティリスク)
find / -type d -perm -o=w -not -path "/tmp/*" -not -path "/var/tmp/*" 2>/dev/null

# 24時間以内に変更されたファイル(インシデント対応)
find / -type f -mtime 0 -not -path "/proc/*" -not -path "/sys/*" 2>/dev/null

パターン4: Webプロジェクトのバッチ処理

bash
# すべてのPNGをWebPに変換(-execでシェルを呼ぶ)
find public/images -name "*.png" -exec sh -c 'cwebp -q 80 "$1" -o "${1%.png}.webp"' _ {} \;

# ソースファイルの改行コードを修正
find src/ -name "*.ts" -exec dos2unix {} +

# ロケール間で重複するファイル名を検出
find content/ -name "*.mdx" -printf "%f\n" | sort | uniq -d

パターン5: Git操作

bash
# コミット前に未追跡の大きなファイルを発見
find . -maxdepth 3 -size +5M -not -path "./.git/*" -not -path "*/node_modules/*" -type f

# .gitignoreに入れるべきファイルを検索
find . -name ".env*" -o -name "*.pem" -o -name "*.key" | grep -v ".git/"

パターン6: ログ管理

bash
# 7日以上前のログを圧縮
find /var/log -name "*.log" -mtime +7 -exec gzip {} \;

# 90日以上前の圧縮ログを削除
find /var/log -name "*.log.gz" -mtime +90 -delete

# 直近1時間以内に更新されたログ(トラブルシュート用)
find /var/log -name "*.log" -mmin -60

find vs fd: 使い分け

fdはRust製のモダンな代替ツールで、パターンベースの検索では圧倒的に速い。僕の使い分けはこう:

場面使うツール理由
ファイル名のさっと検索fd約20倍速い、構文もシンプル
パーミッション/所有者で検索findfdは非対応
複雑な日時条件find-mmin-newer
結果に即アクション(-exec)findパイプ不要で組み込み
.gitignore を尊重fdデフォルトで有効
スクリプトの可搬性findどの環境にもある
CI/CDパイプラインfind追加インストール不要
bash
# fdで同じことをする場合
fd "\.ts$" src/          # fd: 速い、シンプル
find src/ -name "*.ts"   # find: どこでも動く

# findにしかできないこと
find / -type f -perm -4000 -mtime -1 -exec ls -la {} +

fdやその他のモダンツールについてはモダンRust CLIツールを参照。

FAQ

-name と -path の違いは?

-name はファイル名(最後の要素)のみにマッチ。-path はパス全体にマッチする。ディレクトリ構造を含めて指定したいときは -path を使う: find . -path "*/config/*.json"

グロブの代わりに正規表現を使うには?

-name の代わりに -regex を使う。デフォルトはEmacs形式の正規表現で、パス全体に対してマッチする:

bash
# .ts または .tsx ファイルを正規表現で検索
find . -regex ".*\.\(ts\|tsx\)$"

# 拡張正規表現(POSIX ERE)のほうが書きやすい
find . -regextype posix-extended -regex ".*\.(ts|tsx)$"

「Permission denied」エラーが大量に出るのはなぜ?

読み取り権限のないディレクトリを検索しているから。2>/dev/null で標準エラーをリダイレクト: find / -name "file" 2>/dev/null。または -readable(GNU拡張)で読めないエントリをスキップ: find / -readable -name "file"

-exec ; と -exec + の違いは?

\; はファイルごとに1回コマンドを実行。+ はできるだけ多くのファイルを1回にまとめる(xargsと同様)。+ のほうがほぼ常に高速。\; が必要なのは、シェルコマンドでファイルごとに異なる処理をする場合のみ。

findでシンボリックリンクをたどるには?

-L フラグを使う: find -L /path -name "*.conf"。デフォルトではfindはシンボリックリンクをたどらない。循環リンクがあると無限ループになるので注意。

find -delete は安全?

-print で先にテストすれば安全。find -delete は深さ優先でファイルを完全に削除する——ゴミ箱はない。必ず find <条件> -print で確認してから -print-delete に置き換える。

ファイルの中身(内容)で検索するには?

find 自体は内容検索ができない。grepと組み合わせる:

bash
find . -name "*.ts" -exec grep -l "useState" {} +

内容検索だけなら grep -rrg を直接使ったほうが速い。

findの結果を件数制限するには?

find に組み込みのlimitはないが head にパイプすればいい:

bash
find . -name "*.log" | head -5

findhead が十分な結果を得た後も実行を続ける(SIGPIPEを受け取る)。大規模検索で最初のマッチだけ欲しい場合、-quit(GNU拡張)を使う: find . -name "target" -print -quit

まとめ

find は覚えるほど手になじむコマンド。特に使用頻度が高いパターン:

  • find . -name "*.ext" -type f — 基本中の基本、名前でファイルを探す
  • find . -mtime +30 -delete — 日時ベースのクリーンアップ
  • find . -path "*/node_modules" -prune -o -name "*.ts" -print — ディレクトリスキップで高速化
  • find . -name "*.log" -exec gzip {} + — パイプなしでバッチアクション
  • find . -name "*.txt" -print0 | xargs -0 commandxargsへの安全なパイプライン

xargsgrepを知っていれば、find を加えてファイル処理の三種の神器が揃う。sed/awkでテキスト変換、fzfでインタラクティブ選択と組み合わせれば、ターミナルから出ずに大抵のファイルシステム操作ができる。