よくある話:夜中の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は、コマンドが複数の引数を一度に受け取れる場合(rm、chmodなど)に使用。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は複雑ではありません。正しいシンタックスさえ押さえれば十分です。
