Linuxの高度なfindコマンド:複数条件でのファイル検索と-execおよびxargsによる一括自動処理

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

よくある話:夜中の2時にサーバーのディスクが満杯になる

会社の古いCentOS 7サーバーを管理していた頃、何度も経験したくない光景がありました。夜中の2時にアラームが鳴り、/varディスクが98%になって、アプリケーションがエラーを吐き始める。ターミナルを開いて、df -hで確認した後、ls -lhでディレクトリを一つずつ手探りで調べていく。6ヶ月前のログファイルが40GB近くを占有していたと判明するまで、1時間以上かかりました。

最初からfindコマンドを正しく使えていれば、この作業は30秒で済んでいたはずです。find /var -name "*.log"のような基本的な使い方ではなく、複数条件の組み合わせ、時間とサイズでの絞り込み、そのままxargsにパイプして一括処理まで行うフルバージョンの話です。

なぜ基本的なfindでは解決できないのか

最初の数年間、私もfindをこのレベルでしか使っていませんでした:

find /var/log -name "*.log"

実際の問題はそう単純ではありません。単に.logファイルを探したいのではなく、30日以上前の.logファイルで、サイズが100MB以上、かつnginxユーザーに属するものを探したいのです。上記のコマンドを実行すると数百件のファイルが出てきます。新しいものも古いものも、小さいものも大きいものも混ざった状態で、結局手作業で絞り込む羽目になります。

見つけた後はどうしますか?1行ずつコピーしてrmする?200ファイルあったらどうするのでしょう。高度なfindはこの両方を解決します。最初から正確に絞り込み、同じコマンド内で即座に処理まで行えます。

findコマンドの高度なフィルタリングオプション

時間によるフィルタリング

サーバーの整理をするときは、まず必ずこのオプション群を使います:

  • -mtime +N — N日以上前に更新されたファイル
  • -mtime -N — 直近N日以内に更新されたファイル
  • -atime +N — N日以上アクセスされていないファイル
  • -newer file_ref — 参照ファイルより新しいファイル
# 30日以上前のログファイルを検索
find /var/log -name "*.log" -mtime +30

# 60日間アクセスされていないファイルを検索
find /home -atime +60 -type f

サイズによるフィルタリング

単位はc(バイト)、k(KB)、M(MB)、G(GB)が使用できます:

# 100MB以上のファイルを検索
find /var -size +100M -type f

# 1KB未満のファイルを検索(空のゴミファイルが多い)
find /tmp -size -1k -type f

パーミッションとオーナーによるフィルタリング

# SUIDビットを持つファイルを検索 — セキュリティ上重要
find / -perm -4000 -type f 2>/dev/null

# nginxユーザーに属するファイルを検索
find /var/log -user nginx -type f

# パーミッション777のファイルを検索(設定ミスの可能性が高い)
find /var/www -perm 777 -type f

複数条件の組み合わせ

この部分を活用している人は少ないですが、こここそfindが真に強力になる場面です。論理演算子-and(デフォルト)、-or-notをサポートしています:

# 暗黙のAND:.logファイル かつ 50MB以上 かつ 7日以上前
find /var/log -name "*.log" -size +50M -mtime +7

# OR:.logまたは.gzファイルを検索
find /var/log \( -name "*.log" -o -name "*.gz" \) -mtime +30

# NOT:.log以外のファイルを検索
find /var/log -not -name "*.log" -type f

# 複雑な組み合わせ:.logまたは.txtで14日以上前かつ10MB以上
find /data \( -name "*.log" -o -name "*.txt" \) -mtime +14 -size +10M

結果の処理:-exec対xargs

-execを使う:シンプルで特殊なファイル名にも安全

-execは見つかったファイルに対して1つずつコマンドを実行します。{}はファイル名のプレースホルダーで、\;で終了します:

# 30日以上前のログファイルを削除
find /var/log -name "*.log" -mtime +30 -exec rm -f {} \;

# 全PHPファイルのパーミッションを644に変更
find /var/www -name "*.php" -exec chmod 644 {} \;

# 見つかったファイルの詳細を表示
find /tmp -size +10M -exec ls -lh {} \;

# 古いログファイルを削除せず圧縮する
find /var/log -name "*.log" -mtime +7 -exec gzip {} \;

\;+に変えると、複数のファイルを1回の実行にまとめられます。プロセスのfork回数が減るため、速度が大幅に向上します:

# 複数ファイルを1回のrm呼び出しで処理(より効率的)
find /var/log -name "*.log" -mtime +30 -exec rm -f {} +

xargsを使う:より細かい制御が必要な場合

処理するファイル数を制限したり、複数のプロセスを並列実行したりする必要がある場合は、xargsにパイプします:

# findの結果をxargsにパイプしてrmで削除
find /var/log -name "*.log" -mtime +30 | xargs rm -f

# スペースを含むファイル名に安全:-print0と-0を使用
find /data -name "*.tmp" -print0 | xargs -0 rm -f

# -Pで並列実行:4ファイルを同時処理
find /backup -name "*.sql" -print0 | xargs -0 -P 4 gzip

# 1回あたりのファイル数を制限:-n 10
find /logs -name "*.log" -print0 | xargs -0 -n 10 rm -f

どちらを使うべきか

  • -exec {} \;は、コマンドがファイルを1つずつ処理する必要がある場合(gzip、特定ディレクトリへのmvなど)に使用
  • -exec {} +またはxargsは、コマンドが複数の引数を一度に受け取れる場合(rmchmodなど)に使用。fork回数が減り、明らかに高速
  • ファイル名にスペースや特殊文字が含まれる可能性がある場合は、xargs -0-print0を組み合わせて使用。本番環境でのベストプラクティス

実践的なシナリオ向けの完全なパイプライン

定期的なサーバーログのクリーンアップ

これが、あの夜明け3時まで起きていた後に書いたスクリプトです。毎週cronで実行しており、それ以来ログでディスクが満杯になったことはありません:

#!/bin/bash
# 30日以上前かつ1MB以上のログを削除
find /var/log -name "*.log" -mtime +30 -size +1M -print0 | xargs -0 rm -f

# 7〜30日前のログを圧縮(保持しつつ容量を節約)
find /var/log -name "*.log" -mtime +7 -mtime -30 -size +1M -print0 | xargs -0 gzip

# 90日以上前の.gzファイルを削除
find /var/log -name "*.gz" -mtime +90 -delete

セキュリティチェック:危険なファイルを探す

# SUID/SGIDを持つファイルを全検索 — 新しいパッケージインストール後に実行
find / -type f \( -perm -4000 -o -perm -2000 \) 2>/dev/null | sort

# Webルート内のworld-writableなファイルを検索
find /var/www -perm -o+w -type f

# 24時間以内に作成されたPHPファイルを検索 — ウェブシェルの検出
find /var/www -name "*.php" -mtime -1 -type f

ファイルの一括リネームまたは移動

# 1年以上前の画像ファイルをアーカイブディレクトリに移動
find /uploads -name "*.jpg" -mtime +365 -exec mv {} /archive/images/ \;

# オーナーを一括変更
find /var/www/html -type f -exec chown www-data:www-data {} +

# 空のディレクトリを検索して削除
find /data -type d -empty -delete

本番環境でfindを使う際に覚えておくべきパターン

私が最もよく繰り返すコマンドの構造:

find [パス] [フィルタ条件] -print0 | xargs -0 [処理コマンド]

通常のパイプの代わりに常に-print0 | xargs -0を使用してください。異常なファイル名にも安全です。実際に削除や変更を行う前に、最後のコマンドをls -lhに置き換えてリストを確認しましょう。本番環境では、誤ったrmコマンドを取り消す方法はありません。

以前は1時間以上かかっていたメンテナンス作業が、今では5行のスクリプトでcronが自動実行しています。findは複雑ではありません。正しいシンタックスさえ押さえれば十分です。

Share: