Mastering Shell Scripting: Comprehensive Guide

You are an expert shell scripting and command-line tutor. Your job is to help me master shell scripting, one-liners, and practical command-line techniques for my work.

In this thread, I will provide shell commands or topics that I would like to learn. For each one, please provide a comprehensive explanation that is thorough enough that I won’t need to consult any other resources. The explanation should cover:

  1. Core functionality and purpose
  2. All options and features (except extremely obscure ones), with clear descriptions
  3. Examples that cover every single option or feature you provided above, mixing basic to advanced, each with expected output or behavior
  4. Common use cases and best practices
  5. Potential pitfalls or gotchas to avoid

For all features and examples:

  • Mark any non-POSIX features with [Non-POSIX]

  • Specify the shell name for shell-specific features (e.g., [bash], [zsh], [ksh], [fish], etc.)

  • Tag implementation/platform differences if needed (e.g., [GNU], [BSD], [BusyBox], [macOS], [Linux], etc.)

Include at least 5 exercises of varying difficulty levels with solutions at the end.

Please provide complete, self-contained explanations in each response (don’t split them across multiple messages). For this first response only, please confirm that you understand these instructions without providing examples. From the second message onward, provide full explanations with examples and outputs. Please reply in Japanese.

You·Sep 1, 2025

承知しました。 今後は、各シェルコマンドやトピックについて、以下の内容を盛り込んだ包括的な解説を行います:

  • コアな機能と目的

  • 主なオプションや機能(極端にマイナーなものを除き、全て網羅して明確に説明)

  • 具体例(上記の全オプションや機能を網羅する形で、基本的な使い方から上級者向けテクニックまで示し、それぞれ期待される出力や動作も記載)

  • 一般的な使用例とベストプラクティス(実務での活用方法や効率的な使い方の提案)

  • 注意点や落とし穴(誤用しやすい点や陥りがちなミス、その回避策)

また、説明内ではPOSIX標準外の機能には [Non-POSIX] と明記し、シェル固有の機能(例:[bash], [zsh] など)は該当シェル名をタグとして付け加えます。GNU/BSD、BusyBoxやプラットフォーム([Linux], [macOS] 等)の実装差がある場合も適宜注釈し、明確に区別します。

最後に、各トピックに関して少なくとも5問の練習問題(難易度は初級から上級まで)を作成し、その解答と解説を回答の末尾に示します。これらの回答は全て一つのメッセージ内で完結するようにし、他の資料を参照しなくても十分理解できる内容を目指します。今後ともよろしくお願いいたします。

o3 Deep Research·Sep 1, 2025

find

You·today at 9:01 am

findコマンド徹底解説

概要: findコマンドの基本機能と目的

findコマンドは、指定したディレクトリツリー内でファイルやディレクトリを検索する強力なユーティリティです。パス名、ファイル名のパターン、種類、サイズ、所有者、更新日時などさまざまな条件でフィルタリングし、一致する項目に対して処理を実行できます。findは再帰的にディレクトリを巡回し、POSIX環境はもちろんLinux、macOSなど多くのUNIX系OSで利用可能です。形式は一般に次のようになります:

find [<検索開始パス>...] [<式(検索条件とアクション)>...]
  • 検索開始パス: どのディレクトリから検索を始めるかを指定します。複数指定も可能です(省略した場合、多くの実装でカレントディレクトリ.が使われますが、POSIX規格では未定義の挙動のため明示指定が推奨されます)。

  • 式(expression): 検索条件(オプションやテスト)とアクションの組み合わせです。findでは条件にマッチしたファイルを自動的に表示(デフォルト動作)できますし、後述のアクションを指定してさまざまな操作を実行できます。

findコマンドの特徴:

  • 柔軟な条件指定: 名前やパスのパターン一致、ファイル種別(通常ファイル、ディレクトリ、シンボリックリンク等)、サイズやタイムスタンプ、パーミッション、所有者など、細かな条件で検索可能です。

  • 再帰的検索: 指定ディレクトリ以下のすべての階層を探索します(深さを制限するオプションもあります)。

  • ブール論理の組み合わせ: 複数の条件を論理AND/OR/NOTで組み合わせ、複雑な検索クエリを構成可能です。

  • 結果への操作適用: 条件に合致したファイルに対し、表示(-print)以外にも削除(-delete)、移動・実行(-exec)、一覧表示(-ls)など直接アクションを実行できます。

  • POSIX標準で定義された基本機能と、多くの拡張機能(実装依存のオプション)が存在します。拡張機能を使う場合は移植性に注意が必要です。

主なオプション・機能の一覧

findコマンドで使われる主なオプションや式(プライマリ)の機能と意味を、カテゴリー別に整理して説明します。POSIX標準にない拡張機能には**[Non-POSIX]と明記し、特定の実装に固有のものは[GNU][BSD]**, [macOS](macOSのfindはBSD系)等のタグで示します。

検索範囲・探索動作を制御するオプション

  • -H, -L, -P: シンボリックリンクの扱いを制御します(-Pはデフォルト動作でリンクそのものを扱う)。

    • -H: コマンドライン引数として指定したパスがシンボリックリンクの場合、それを辿って実際の参照先を検索対象にします。探索中に出てくるリンクは辿りません (pubs.opengroup.org) (pubs.opengroup.org)。
    • -L: [POSIX] すべてのシンボリックリンクを辿ります(リンク先の実ファイル/ディレクトリを対象にする)。存在しないリンクの場合はリンク自体を対象とします (pubs.opengroup.org)。
    • -P: 指定したリンクを辿らずにリンクそのものを扱います(デフォルト)。: 複数指定された場合、後に指定したオプションが有効になります (pubs.opengroup.org)。
  • -maxdepth <n> [Non-POSIX][GNU][BSD]: 再帰的検索の深さを制限します。-maxdepth 0なら開始ディレクトリ自身のみ、-maxdepth 1なら開始ディレクトリとその直下まで、といった具合に最大<n>階層まで探索します。

  • -mindepth <n> [Non-POSIX][GNU][BSD]: 最低探索深度を指定します。開始点から<n>階層目より浅い場所は検索対象にしません(例: -mindepth 1なら開始ディレクトリ自身は除外しその中身から検索開始)。

  • -mount / -xdev [POSIX]: 他ファイルシステムへの横断を防止します。-mount(システムによっては-xdevと同義)を指定すると、検索開始パスとデバイスIDが異なるディレクトリには降りていきません (pubs.opengroup.org) (pubs.opengroup.org)。例えばLinuxでは-mount-xdevが同じ動作で、別のファイルシステム(例: マウントされた外部ボリュームや/procなど仮想FS)は検索から除外できます。

  • -prune [POSIX]: 該当ディレクトリをこれ以上探索しないようにします。常に真を返す特殊なプライマリで、ディレクトリにマッチした場合にそのディレクトリ以下を再帰的に探索しないようにカットします (pubs.opengroup.org) (pubs.opengroup.org)。-prune自体はアクションではないため、単独で使う場合は-print等と組み合わせます(後述の使用例参照)。注意: -depthオプションが指定されていると-pruneは無視されます (pubs.opengroup.org)。

  • -depth [POSIX]: ディレクトリを深さ優先で処理します。通常findはディレクトリを見つけると先にそのディレクトリ自身を処理してから中に入りますが、-depth指定時は中のエントリを全て処理した後でディレクトリ自身を処理します (pubs.opengroup.org)。ディレクトリ内のファイルを先に処理したい場合や、-pruneを無効化して全て探索したい場合に使います(例: ディレクトリを削除する前に中身を削除する場合など)。

名前・パスに関する検索条件

  • -name <パターン>: ファイル名(ベース名)が指定のグロブパターンとマッチした場合に真になります (pubs.opengroup.org)。ワイルドカード(*, ?, [...])が利用可能ですが、シェル展開を防ぐためクォートするのが基本です(例: -name "*.log")。大文字小文字は区別されます。

  • -iname <パターン> [POSIX]: -nameの大文字小文字を無視する版です (pubs.opengroup.org)。例えば-name "FILE.TXT"だとFILE.TXTにしかマッチしませんが、-iname "FILE.TXT"ならfile.txtFile.Txtなどにもマッチします。

  • -path <パターン>: パス全体(検索開始点からの相対パス)がパターンにマッチした場合に真になります (pubs.opengroup.org)。/も通常の文字として扱われ、パターン内の*は階層をまたいでマッチする可能性があります。例えば-path "*/backup/*.zip"は「どこかのbackupディレクトリ配下にある.zipファイル」にマッチします。: より古いGNU版では同等機能として-wholename (stackoverflow.com)というオプション名もありましたが、-pathがPOSIX標準に追加されたため現在は-pathを使うのが移植性が高いです (stackoverflow.com)。

  • -regex <正規表現> [Non-POSIX][GNU]: パス全体が指定した正規表現にマッチする場合に真になります。デフォルトではGNU findはEmacs風の正規表現構文を使用します(-regextypeオプションでPerl風などに変更可能 [Non-POSIX][GNU])。例えば、-regex ".*\.txt$"とすると「パス全体が.txtで終わるもの」を検索できます(「.*」が途中のディレクトリや文字列にマッチ) (unix.stackexchange.com)。正規表現はグロブより表現力がありますが、実装依存のため互換性に注意してください(BSD系でも-regex (man.freebsd.org)がありますが正規表現エンジンがPOSIX EREになるなどの違いがあります)。

  • -iregex <正規表現> [Non-POSIX][GNU][BSD]: 大文字小文字を区別しない正規表現マッチです(-regexのケースインセンシティブ版)。

ファイルの種類・属性に関する条件

  • -type <c>: ファイルの種類がcである場合に真になります (pubs.opengroup.org)。cは以下の1文字で指定:

    • f: 通常のファイル (regular file)
    • d: ディレクトリ
    • l: シンボリックリンク
    • b: ブロックデバイス
    • c: キャラクタデバイス
    • p: 名付けパイプ(FIFO)
    • s: ソケット
      例: -type fで通常ファイルのみ、-type dでディレクトリのみを対象にできます。
  • -xtype <c> [Non-POSIX][GNU]: シンボリックリンクをデリファレンスして種類を判定します。 (unix.stackexchange.com)通常、-type lでシンボリックリンクそのものを検索できますが、-xtypeを使うとリンクの指す先が存在する場合はその種類を、存在しない(壊れたリンク)場合はリンク自体の種類lを返します。例えばGNU findで壊れたシンボリックリンクだけを探すには-xtype lを使用します(リンク先が存在するものは-xtypeではそれぞれfd等と判定されるため除外されます) (unix.stackexchange.com)。

  • -empty [Non-POSIX][GNU][BSD]: 空のファイル、または中身のないディレクトリを検索します (man.freebsd.org)。ファイルサイズが0バイトの通常ファイルや、中に他のファイル(...以外)がないディレクトリが該当します。

  • -perm <mode>: パーミッション(モード)に関する条件です。<mode>の指定方法によって判定内容が変わります (pubs.opengroup.org) (pubs.opengroup.org):

    • <mode>をそのまま(頭に-/を付けず)記述するとパーミッションが正確に一致した場合に真となります。例えば-perm 644はモードがちょうど0644(rw-r--r--)のファイルだけにマッチします。
      • 数字は8進数表記で指定します(先頭に0が無くても通常8進とみなされますが、明確にするため0644のように書くこともできます)。
      • あるいは記号表記も可能です (pubs.opengroup.org)。記号表記ではまず全ビット0から開始し、例えばu=rwx,g=rx,o=rのようにchmod風に指定するとそのモード値(0754)になります。単独指定時はこの完全一致判定になります。
    • <mode>の前に**-(ハイフン)**を付けると、「指定したビットが最低限すべてセットされている」場合に真となります (pubs.opengroup.org)。これは一種の「包含」条件です(モードのその他のビットは問わない)。
      • 数字例: -perm -4000SUIDビットが設定されているファイルをすべて検索します(他のビットは自由)。
      • 記号例: -perm -u=wは「所有者に書き込み権限(u+w)が付与されている」ファイルを検索します(グループやその他の権限は問わず、それぞれの書き込みビットが付いているかどうかのみ判定)。
    • GNU拡張として<mode>の前に**/を付ける指定もあります (man.freebsd.org)(FreeBSD版などでも同様の機能あり)。これは「指定したビットのいずれか**がセットされている」場合に真となります(論理和的マッチ)。例えばGNU findでは-perm /222とすると「いずれかのユーザ区分に書き込み権限があるファイル」を意味します(older GNU findの-perm +modeと同等ですが、+は古い記法のため現在は/推奨)。
    • 特殊ケース: -perm -0は常に真(必要なビット指定なし)です。
    • 注意: -perm 0644のように指定するとまさにそのモードだけに一致するため、例えば実行ビットを持つ644とは異なる655(rw-r-xr-x)などはマッチしません。「少なくとも...を含む」場合は-付きや/付きの形式を使う必要があります。また、一部古いfindでは+mode記法が使われますが新しいバージョンでは非推奨です。
  • -links <n>: ハードリンクの数がnである場合に真になります (pubs.opengroup.org)。通常ファイルは1(自分自身のリンクのみ)ですが、複数のパスから参照されるハードリンクの場合リンク数が増えます。ディレクトリは基本2以上(自分の親からの参照..などがあるため)になります。+-を付けて「より多い・少ない」も指定できます。例えば-links +1複数のハードリンクを持つファイルを探せます。

  • -user <ユーザ名>, -group <グループ名>: 所有者(オーナー)ユーザまたはグループが指定した名前(または対応するIDの場合もあり)と一致する場合に真になります (pubs.opengroup.org)。名前が指定された場合、システムにそのユーザ/グループが存在しなければ数値として解釈されます (pubs.opengroup.org)。例: -user aliceはオーナーがaliceのファイル、-group 0はグループID 0(典型的にはrootグループ)のファイルを検索します。

  • -uid <数値>, -gid <数値> [Non-POSIX][GNU]: オーナーのユーザIDまたはグループIDが指定した数値と一致する場合に真になります。-user/-groupと異なり名前ではなくIDを直接指定する用途です(GNU find拡張。一部BSD系では-user/-groupに数値を渡すことで同様の動作)。

  • -nouser, -nogroup: 所有者ユーザまたはグループIDに対応する実在のユーザ/グループが存在しない(孤児ファイル)場合に真となります (pubs.opengroup.org) (pubs.opengroup.org)。例えばシステムからユーザが削除された後に残ったファイルなどを検出できます。

  • -readable, -writable, -executable [Non-POSIX][GNU]: ファイルが現在のユーザにとって読み取り可能/書き込み可能/実行可能である場合に真になります。これはアクセス権を実際にチェックするテストで、-permによる単純なモードビット判定とは異なり、ACLなどOSの実際のアクセス制御も考慮します。

  • -inum <inode番号> [Non-POSIX][GNU][BSD]: inode番号が指定した値と等しいファイルを検索します。ls -iで表示されるinode番号を使って、そのinodeを持つ全てのパス(ハードリンクなど)を見つけるのに利用可能です。

  • -samefile <パス> [Non-POSIX][GNU]: <パス>同じinodeを指しているファイルを検索します(すなわち<パス>とハードリンク関係にあるファイルを全て列挙)。例えばfind . -samefile ./example.txtとするとexample.txtの他に同じinodeを共有する別名のファイルがあれば見つかります。

時刻(タイムスタンプ)に関する条件

  • -mtime <n>: 最終修正時刻(内容が最後に変更された日時)からの経過日数で判定します (pubs.opengroup.org)。nは24時間単位の日数で、ファイルの修正時刻find実行時刻との差を切り下げ整数日になった値と比較されます (pubs.opengroup.org)。

    • -mtime 0は「実行日と同じ日付内に修正されたファイル」(直近24時間以内)を意味します。
    • -mtime 1は「1日前ちょうど~2日前未満に修正」(24~47時間前)のファイル、
    • -mtime +7は「7日よりも前(8日以上前)に修正」されたファイル、
    • -mtime -1は「過去24時間以内に修正」されたファイルを表します。
      +は「より大きい(経過日数が多い = 古い)」、-は「より少ない(日数が少ない = 新しい)」、数値単独はちょうどその日数に相当する範囲を指します (pubs.opengroup.org)。
  • -atime <n>: 最終アクセス時刻(読み取りなどで最後にアクセスされた日時)について-mtimeと同様の指定方法で判定します (pubs.opengroup.org)。

  • -ctime <n>: ファイル状態変更時刻(inode自体が変更された時刻。パーミッション変更・所有者変更やリンク数変化など含む)について判定します (pubs.opengroup.org)。-ctimeは「属性または内容が変化した時刻」を見るもので、内容変更を含む場合は-mtimeと同じになりますが、所有者変更のみの場合など-mtimeには現れない変更も検出できます。

  • -mmin <分>, -amin <分>, -cmin <分> [Non-POSIX][GNU][BSD]: 上記の各時刻について分単位で判定します。-mmin -30なら「過去30分以内に更新されたファイル」、-amin +1440なら「最終アクセスが24時間より前のファイル」と細かな指定が可能です。

  • -newer <基準ファイル>: 修正時刻が指定したファイルより新しい場合に真になります (unix.stackexchange.com)。例えば-newer target.txtは「target.txtより後に修正されたファイル」を探します。

    • 応用: -newer fileA ! -newer fileBと組み合わせれば「fileAより新しく、fileBより古い(fileA~fileBの間に変更された)」ファイルを探すこともできます。
  • -anewer <基準ファイル>, -cnewer <基準ファイル> [Non-POSIX][GNU][BSD]: アクセス時刻または状態変更時刻が基準ファイルより新しい場合に真となります(FreeBSD版では-anewer-neweramのエイリアス (man.freebsd.org))。GNU findでは代わりに後述の-newerXYで細かく指定可能です。

  • -newerXY <基準ファイル> [Non-POSIX][GNU]: 基準ファイルのある種の時刻と、検索対象ファイルの別種の時刻を比較できます。Xは検索対象ファイルの時刻、Yは基準ファイルの時刻を表す1文字(a=アクセス, c=状態変更, m=修正)です。例えば-newermt <日時文字列>のように使うと現在時刻ではなく指定した日時Ytの場合は基準が指定日時となる)と比較することもできます。この機能を使えば「2023-01-01以降に更新されたファイル」を-newermt "2023-01-01"で探す、といった指定も可能です。**【注】**日時文字列の解釈は実装依存で、GNU findでは現在ロケールの日時形式やYYYY-MM-DD等を受け付けます。

論理演算子と式のグループ化

  • ! <式> または -not <式> [Non-POSIX]: <式>の条件を論理否定(NOT)します。! -type dのように使えば「ディレクトリではない」ものを意味します。-notはGNUやBSDで使える別表記ですが、POSIX互換性を考えると単項演算子!の方が良いでしょう(ただし!をシェルで直接使うと履歴展開と衝突する場合があるため、トラブルがある場合は\!とエスケープするか-notで代用します)。

  • -a (AND), -o (OR): 論理積・論理和を指定します。findの式では**デフォルトで隣接する条件はAND(論理積)として扱われます (pubs.opengroup.org)。そのため通常は-aを明示しなくてもよいですが、複雑な式では明示した方が読みやすい場合もあります。-oは論理和(OR)を表し、前の条件または後の条件のどちらかが真ならマッチとみなします。優先順位に注意: ANDはORよりも強く結合する(ANDの方が先に評価される)ため、必要に応じて()**でグループ化します (unix.stackexchange.com)。

  • ( <式1> -o <式2> ): カッコを使って式のグループ化ができます。シェルに解釈されないよう**\(\)のようにバックスラッシュでエスケープするか、引用符で囲んでください。例えばfind . \( -name "*.c" -o -name "*.h" \) -type fは「拡張子が.cまたは.h通常ファイル**」を探します。グループ化しないと-oの優先順位によって意図しない論理式になり得るので、複数条件の組み合わせでは積極的に( )を使うのがベストプラクティスです。

アクション(検索結果に対する処理)

  • -print: マッチしたパス(ファイル名)を標準出力に表示します。常に真を返し、デフォルトでは各結果を改行区切りで表示します (pubs.opengroup.org)。ほとんどのfind実装では明示的に式を指定しない場合、自動的に-printが付加されます (pubs.opengroup.org)。また、明示した式に-exec, -ok, -printのいずれも含まれない場合も、最後に自動的に-printが補われます (pubs.opengroup.org)。この仕様により「find <dir>としただけでは何も表示されない」といった初心者のミスを防いでいます (pubs.opengroup.org)。

  • -print0 [Non-POSIX][GNU][BSD]: マッチしたパス名の後に**ヌル文字(\0)**を出力します (pubs.opengroup.org)。各結果を改行ではなくヌル文字で区切るため、ファイル名に改行や空白が含まれていても安全に区切りを認識できます。主にxargs -0などと組み合わせて大量のパスを安全に別プログラムに渡す用途で頻用されます。: POSIX仕様にはなかった拡張ですが、現在多くの実装でサポートされており、2024年版POSIXにも追加されました (pubs.opengroup.org)。

  • -exec <コマンド> {} \;: マッチした各ファイルについて、指定した外部コマンドを実行します (pubs.opengroup.org)。{}はプレースホルダで、見つかったファイルのパスに置き換えられます。find{}を含むコマンドラインを組み立て、\;が現れるまでを1つのコマンドとして実行します。例えば-exec rm {} \;は各見つかったファイルに対しrm <そのファイル>を実行して削除します。

    • -exec ... {} + [Non-POSIX]: コマンド実行の区切りを;ではなく+で終わらせると、可能な限り複数の結果をまとめて一度のコマンド実行で処理します (pubs.opengroup.org)。これは[GNU]や[BSD]実装でサポートされ、POSIX規格にも組み込まれました(古いUNIXでは未対応の場合があります)。例えば-exec echo {} +とすると、見つかった全ファイルパスを空白区切りで並べて一回のechoコマンドに渡します。大量のファイルに対して一つ一つ実行するより高速で、コマンドライン長がシステム上限を超えない範囲でまとめられます。注意: +を使う場合はコマンドの最後の引数に{}を置かなければなりません(find{}を展開して最後に+が来る形式のみ許容)。
    • -execで起動されるコマンドのカレントディレクトリは、findを実行したときのカレントディレクトリと同じです (pubs.opengroup.org)(find .している場合はその.に相当するディレクトリではなく、findを呼んだ場所)。コマンド内で相対パスを扱う際は注意が必要です。
  • -execdir <コマンド> {} \; [Non-POSIX][GNU][BSD]: -execに似ていますが、実行するコマンドのカレントディレクトリを対象ファイルの存在するディレクトリに変更してから実行します (man.freebsd.org)。また{}で渡されるパスはそのディレクトリ内での相対パス(先頭に./が付く)になります。-execdir-execよりセキュアで、特に{} +で多数のファイルを扱う場合にコマンド引数の上限を超えにくい利点もあります。

    • 例: -execdir sh -c 'echo "$PWD で $1 を処理中"' sh {} \;とすると、各ファイルのあるディレクトリに入りそのパスでメッセージを出力できます。-execではすべて起動元のディレクトリで実行されるため、この違いは顕著です。
    • -ok, -okdir: -exec/-execdirのインタラクティブ版です。動作は同様ですが、実行する前にユーザに<cmd>?のように実行確認を求めます (pubs.opengroup.org)。y(yes)と入力された場合のみコマンドを実行し、nならスキップします。誤って削除してしまう危険を避けたい時などに有用ですが、自動化には不向きです。: -okdirもGNUおよびBSD拡張で、+による一括実行はサポートされません(必ず1件ずつ確認)。
  • -delete [Non-POSIX][GNU][BSD]: 該当ファイルを即座に削除します (man.freebsd.org)。-exec rm {} \;と似ていますが、findが内蔵動作として行うため効率的です。常に真を返し、ディレクトリの場合は空でなければ削除に失敗します (man.freebsd.org)(安全のため、rm -rのような挙動はしません)。注意: -deleteは式の中で出現した時点で即削除を行うため、他の条件と組み合わせる場合は最後に置くのが原則です (man.freebsd.org)。例えば-delete -type fの順だと、-deleteが先に実行されディレクトリも削除しようとして思わぬ結果を招く可能性があります。-delete使用時は-depthオプションが自動的に有効になる(GNU findの場合)ため、-pruneとの併用もできません (man.freebsd.org)。

  • -ls [Non-POSIX][GNU][BSD]: 該当ファイルをls -dilsに似たフォーマットで1行ずつ表示します。出力にはinode番号、ブロックサイズ、パーミッション、リンク数、所有者、グループ、サイズ、日時、パスが含まれます。例えば:

    $ find . -maxdepth 1 -type f -ls
          26      0 -rw-r--r--   1 sandbox  sandbox         0 Aug 27 00:09 ./file1.txt
    

    と出力されます。-lsはデバッグや検索結果の詳細確認に便利です。内容形式が固定のため、カスタマイズしたい場合は次の-printfを使うと良いでしょう。

  • -fls <ファイル> [Non-POSIX][GNU]: -lsと同等の内容を標準出力ではなく指定ファイルに出力します。ログを直接ファイル保存したい場合に使います。

  • -printf "<書式>" [Non-POSIX][GNU]: printf(3)に似た書式指定で出力を制御できます。例えば-printf "%p %s bytes\n"とすれば**パス(%p)とサイズ(%s)**を表示する、といった自由なフォーマットが可能です。主な書式指定(%記号)には以下があります:

    • %p: ファイルのパス(検索開始ディレクトリからの相対パス)
    • %f: ファイル名(最終コンポーネントのみ)
    • %H: 検索開始パス
    • %h: ファイルの親ディレクトリ(検索開始点からの相対パス)
    • %m: パーミッションを8進数で(例: 644)
    • %M: パーミッションをls -l形式で(例: -rw-r--r--
    • %u: 所有者のユーザID(数値)、%U: 所有者名
    • %g: グループID、%G: グループ名
    • %s: ファイルサイズ(バイト)
    • %t: 最終修正時刻をyyyy-mm-dd HH:MM:SS +TZ形式で、%Td, %Tbなど細かい指定も可能
    • %T@: 最終修正時刻をUNIXエポック秒で(浮動小数点)
    • \n, \t など: 改行やタブなどのエスケープシーケンス
      など非常に多彩です。-printf改行を自分で入れない限り出力を改行しない点に注意してください(必要なら書式の末尾に\nを入れる)。-printfはPOSIX標準外のGNU拡張なので、移植性が必要な場面では使い過ぎないようにしましょう(macOS標準のBSD findにはありません [macOS])。

以上が主なオプション・式の一覧です。次章では実際の使用例を示しながら各機能の使い方をより具体的に説明します。

使用例(基本から高度な例まで)

以下では、架空のディレクトリツリー「testdir」を例に挙げ、目的別にfindコマンドの使用例とその結果(予想される出力や動作)を示します。各例では、上記で説明したすべての主なオプションや機能を網羅するように取り上げています

まず、例として用いるディレクトリ構成を簡単に示します(testdir以下にいくつかのファイル・フォルダ、シンボリックリンクを配置):

testdir/
├── file1.txt                (サイズ0バイトの空ファイル)
├── file2.log                (削除対象のログファイル; 内容"hello" 6バイト)
├── subdir/                  (サブディレクトリ)
│   ├── a.txt               (内容"world" 6バイト)
│   └── b.sh                (サイズ0バイトの空ファイル)
├── skipme/                  (探索から除外するディレクトリ例)
│   └── secret.txt          (内容"top secret" 11バイト; その他ユーザ書き込み権限あり)
├── emptydir/                (中身のない空ディレクトリ)
├── link -> subdir/a.txt     (ファイルa.txtへのシンボリックリンク)
├── extlink -> /etc/passwd   (ツリー外のパスを指すシンボリックリンク)
└── brokenlink -> missing    (存在しないパスを指す壊れたシンボリックリンク)

1. ファイル名パターンで検索する(-name, -iname
-nameを使って特定のパターンにマッチするファイルを検索できます。ワイルドカードはシェルに解釈されないようクォートするのがポイントです。


# カレントディレクトリ配下で拡張子が".txt"のファイルを検索

$ find testdir -name "*.txt" -print
testdir/subdir/a.txt
testdir/skipme/secret.txt
testdir/file1.txt

testdir以下の.txtファイル(大文字小文字区別)として、a.txt, secret.txt, file1.txtが見つかります。


# 大文字小文字を無視して"FILE"という名前を探す

$ find testdir -iname "FILE*.TXT" -print
testdir/file1.txt

-inameなのでfile1.txtのように小文字でもマッチしています。この例では"FILE*.TXT"に対しfile1.txtが見つかりました。

2. パス全体や正規表現で検索する(-path, -regex


# "subdir"ディレクトリ配下の全ファイルを検索

$ find testdir -path "testdir/subdir/*" -print
testdir/subdir/a.txt
testdir/subdir/b.sh

-path "testdir/subdir/*"testdir/subdir/以下の階層(*subdir/内の任意のファイル名部分にマッチ)に限定して結果を表示します。subdir自身は*の後に文字が無いためマッチせず、内部の2ファイルだけが表示されています。


# 正規表現で".txt"で終わるパスを検索 (GNU拡張)

$ find testdir -regex ".*\.txt$" -print
testdir/subdir/a.txt
testdir/skipme/secret.txt
testdir/file1.txt

パス全体に対して正規表現.*\.txt$(「任意の文字列の後に".txt"で終了」)を適用し、.txtで終わるすべてのファイルが検索できています。結果は-name "*.txt"の場合と同じですが、正規表現なら例えば^testdir\/[A-Za-z0-9]+\.txt$のように細かなマッチ条件も記述可能です。

3. 種類で検索する(-type, -xtype


# シンボリックリンクのみを検索

$ find testdir -type l -print
testdir/link
testdir/extlink
testdir/brokenlink

-type lでシンボリックリンク3つ(link, extlink, brokenlink)が見つかりました。


# 壊れたシンボリックリンクを検索 (GNU拡張)

$ find testdir -xtype l -print
testdir/brokenlink

-xtype lによりリンク先を評価した上でタイプを判定するため、リンク先が存在するlinkextlinkはそれぞれ通常ファイルとして扱われ-xtype lでは除外されます。結果としてリンク切れのbrokenlinkだけがマッチしました。


# ディレクトリのみを検索

$ find testdir -type d -print
testdir
testdir/subdir
testdir/skipme
testdir/emptydir

ディレクトリ(-type d)として、開始点testdir自身と3つのサブディレクトリが表示されます。findは通常、開始パス自身も条件に含めてチェックします。この例ではtestdirがディレクトリなので結果に含まれています。

4. パーミッションや所有者で検索する(-perm, -user/-group


# その他ユーザに書き込み権限があるファイルを検索

$ find testdir -type f -perm -o=w -print
testdir/skipme/secret.txt

-perm -o=wにより、モードビットで「othersに書き込み(w)許可」が付いているファイルを検索しています。例ではsecret.txtが該当し表示されました(secret.txtはモード-rw-r--rw-でothersのwビットが立っています)。シンボリックリンクもモード上は777扱いですが、-type fで通常ファイルに限定しているためリンクは除外されています。


# パーミッションが正確に644のファイルを検索

$ find testdir -type f -perm 0644 -print
testdir/subdir/a.txt
testdir/subdir/b.sh
testdir/file1.txt

-perm 0644はモードが完全に0644と一致するファイルを探します。上記3ファイルはいずれもrw-r--r--(644)であり一致しました。secret.txtはothersに書き込み権限があるため0646となり、この条件ではヒットしません。


# 所有者がsandboxユーザのファイルを検索

$ find testdir -user sandbox -print
testdir/subdir/a.txt
testdir/subdir/b.sh
testdir/skipme/secret.txt
testdir/file1.txt

(上記は出力例です。実際には環境によってユーザ名は異なります。)-user <名前>で所有者名を指定できます。ここでは“sandbox”ユーザ所有のファイルのみを表示しています。-groupも同様です。名前ではなく数値IDを直接指定する場合は環境によりますが、GNU findなら-uid/-gid、あるいは-user '#1000'のように名前の代わりに#ID形式で指定する方法もあります。

5. サイズで検索する(-size


# サイズが0バイトの空ファイルを検索

$ find testdir -type f -size 0 -print
testdir/subdir/b.sh
testdir/file1.txt

-size 0512バイトブロック数で0という意味なので、実質サイズ0バイトのファイルを指します(512バイト未満は切り上げて1ブロックになるため、0ブロック=0バイト)。結果として空のb.shfile1.txtが見つかりました。


# サイズが5バイトを超えるファイルを検索(単位指定の例)

$ find testdir -type f -size +5c -print
testdir/subdir/a.txt
testdir/skipme/secret.txt

-size +5c5バイトより大きい+5)ファイルを意味します(cはバイト単位指定)。a.txt(6バイト)とsecret.txt(11バイト)が条件を満たし表示されました。なお-size 100kのように**k(キロバイト=1024バイト)やM**(メガバイト=1048576バイト)といった単位も使えます (man.freebsd.org)(これはGNU/BSD拡張です)。例えば-size +1Mは1MiBより大きいファイルの検索です。

6. 更新時刻で検索する(-mtime, -mmin, -newerなど)


# 過去1日以内に変更されたファイルを検索

$ find testdir -mtime -1 -print
testdir/subdir/a.txt
testdir/subdir/b.sh
testdir/skipme/secret.txt
testdir/file2.log

この例では、file2.logは最近作成されたログファイルという想定です。)-mtime -124時間以内に修正されたファイルを指し、直近で内容が変更・作成されたファイル全てが表示されています。


# 7日より前に変更されたファイルを検索

$ find testdir -mtime +7 -print
testdir/file1.txt

file1.txtは意図的に更新日時を過去日に設定したものとします。)-mtime +78日以上前に修正されたファイルで、この例ではfile1.txtのみが該当しました。バックアップ用途などで「1週間以上更新がないファイル」を見つける場合に有用です。


# 基準ファイルより新しいファイルを検索

$ find testdir -newer testdir/file1.txt -print
testdir/subdir/a.txt
testdir/subdir/b.sh
testdir/skipme/secret.txt

file1.txtより新しく変更されたファイルを探しています。file1.txtが古い日時なので、他の3ファイルが表示されました。-newerは特定のファイルを基準にできるので、「あるバックアップファイルより更新されているファイルを洗い出す」ような用途に使えます。


# 分単位: 過去10分以内に変更されたファイルを検索

$ find testdir -mmin -10 -type f -print
... (最近編集されたファイルがあれば表示) ...

-mmin -10のように分単位でも指定できます(GNU/BSD拡張)。cronで直近数分の更新を監視したりする場合などに便利です。同様に-amin-cminも利用できます。

7. 複雑な条件の組み合わせ(論理演算と-prune


# ".git"ディレクトリを除外して全てのソースコードを検索

$ find . -path "./.git" -prune -o -name "*.c" -o -name "*.h" -print
./main.c
./include/common.h
... (.git以下は除外されています) ...

上記はカレントディレクトリ以下でGit管理に使われる.gitフォルダを探索から除外し(-prune)、残りから拡張子.cまたは.hのファイルを表示する例です。ポイントは-pruneと他条件を**-o (OR)で繋ぎ**、-pruneを適用する部分にはその場でマッチしたら真(ディレクトリ探索を中止)となるよう常に真で終わるように書くことです。実際の書式は<除外条件> -prune -o <検索条件> -printとなります。例では.gitにヒットした場合-pruneでその配下を探索せずORの右側を評価しません。一方.gitでない場合は-pruneが偽になるのでORの右側に進み、パターンマッチ結果を-printで表示します。


# skipmeディレクトリ配下を除外して".txt"ファイルを検索

$ find testdir -path "testdir/skipme" -prune -o -name "*.txt" -print
testdir/subdir/a.txt
testdir/file1.txt

skipmeディレクトリごと除外しているため、通常ならヒットするsecret.txtが結果に含まれていないことがわかります。このように、特定のディレクトリ(ビルド中の巨大フォルダ、バックアップディレクトリなど)を探索対象から外したい場合に-pruneパターンを先頭で使うのが定石です。注意: -prune-depthと併用できないため、先に-depthを指定していると効かなくなります。


# 複数の条件をAND/ORで組み合わせる例

$ find testdir \( -name "*.txt" -o -name "*.log" \) -type f -size +0 -print
testdir/skipme/secret.txt
testdir/file2.log

こちらは「.txtまたは.logで、かつ通常ファイルでかつサイズが0より大きい」ファイルを探しています。-o(OR)の結合が-a(AND)より弱いため、()で囲んでグループ化しています。結果としてsecret.txt(テキスト, 通常ファイル, サイズ11バイト)とfile2.log(ログ, 通常ファイル, サイズ6バイト)が表示されました。なお-type f -size +0部分はANDで繋がっており省略していますが、明示して-type f -a -size +0と書いても同じ意味です。

8. コマンドを実行する(-exec, -execdir, -ok


# 見つかった各ファイルの行数を数える (wc -l を実行)

$ find testdir -type f -name "*.txt" -exec wc -l {} \;
  0 testdir/subdir/a.txt
 10 testdir/skipme/secret.txt
  0 testdir/file1.txt

-exec <コマンド> {} \;で各結果についてコマンドを実行できます。上記ではテキストファイルそれぞれに対しwc -l <ファイル名>を実行し、その標準出力(行数とファイル名)がターミナルに表示されています。{}は検索で見つかったファイルへの絶対または相対パスに置き換わります(この例ではtestdirを指定しているので出力にはtestdir/...という相対パスが含まれています)。


# "-exec"と"-execdir"の動作比較

$ find testdir -type f -name "secret.txt" -exec ls "{}" \;
testdir/skipme/secret.txt
$ find testdir -type f -name "secret.txt" -execdir ls "{}" \;
./secret.txt

最初の-execではlsコマンドがfindを実行した場所(カレントディレクトリ)で動き、引数としてtestdir/skipme/secret.txtというパスを受け取ったため、そのままフルパスが表示されました。二つ目の-execdirでは、secret.txtの存在するディレクトリskipmeにカレントディレクトリを移してからlsを実行するため、結果は当該ディレクトリ内でのパス(./secret.txt)が表示されています。このように-execdirはコマンドがファイルの所在ディレクトリで実行される点で-execと異なります。


# "-exec"で複数ファイルをまとめて処理する (+ の使用)

$ find testdir -type f -name "*.txt" -exec echo "Found file:" {} \;
Found file: testdir/subdir/a.txt
Found file: testdir/skipme/secret.txt
Found file: testdir/file1.txt

$ find testdir -type f -name "*.txt" -exec echo "Found file:" {} +
Found file: testdir/subdir/a.txt testdir/skipme/secret.txt testdir/file1.txt

上は-exec;+の違いを示した例です。;版ではechoコマンドがマッチしたファイルごとに3回実行され、それぞれ1行ずつ出力しています。一方+版では、3つのパスがまとめて1回のechoに引数として渡されて実行されたため、1回の実行で3ファイル分のパスが空白区切りで出力されました(コマンドによってはこの差で動作が異なる場合がありますが、echoでは問題なく一行に連結されました)。大量の結果に対しては+を使う方が効率的です。ただし、-exec <cmd> {} +で一度に渡されるファイル数はシステムのコマンドライン長制限の範囲に制約されます。そのため極端に多い場合は内部的に複数回に分けて実行されますが、それでも1ファイルずつ実行する;より高速になります。


# 対話的に削除確認をしながらファイルを削除 (-ok の使用例)

$ find testdir -type f -name "*.log" -ok rm {} \;
< rm .../testdir/file2.log > ? y

上記ではまずfile2.logが見つかり、rmコマンドを実行してよいか< ... > ?という確認メッセージが表示されます。yと答えると削除が行われ、nならスキップされます。-okは誤削除防止には有用ですが、非対話で多数のファイルを処理する場合は不向きです。

9. ファイルを削除・アーカイブする(-delete, xargsとの併用など)


# 例: 旧いログファイルを削除する(-deleteの利用)

$ find testdir -name "*.log" -mtime +7 -print
testdir/file2.log
$ find testdir -name "*.log" -mtime +7 -delete
$ find testdir -name "*.log" -print

まず-printで7日より古い.logファイルを確認した後、-deleteで該当するファイルを即時削除しています。file2.logが1つヒットし、-delete実行後にはfindしても何も出力されない(削除された)ことを確認しています。-deleteは簡便ですが危険も伴うため、いきなり使わず一度-printで対象を確認するのがベストプラクティスです。また、削除順序の問題から、ディレクトリを-deleteしたい場合は-depthを併用して子ファイル→親ディレクトリの順で削除するか、単にrm -rを使う方が良いでしょう。


# 例: 7日より古いログをgzip圧縮する(-execの利用)

$ find /var/log -type f -name "*.log" -mtime +7 -exec gzip {} \;

(出力は省略。このコマンドを実行するとマッチした各.logファイルがその場で.log.gzに圧縮されます。)
-execを使えば削除以外にも、例えば古いログを自動的に圧縮することもできます。{} \;によってマッチしたファイルごとに一つずつgzipコマンドが実行されます。ファイル数が非常に多い場合、一つずつ圧縮だと時間がかかるため、GNU Parallelやxargsの-Pオプション等で並列実行する高度な方法もありますが、まずはシンプルなfind -execで確実に処理するのが安全です。


# 例: ".txt"ファイル一覧を作成する(-print0 と xargs の併用)

$ find testdir -type f -name "*.txt" -print0 | xargs -0 ls -l > txt_files.list

xargsの実行には出力がありませんが、txt_files.listにリストが保存されます。)
上記では-print0で各結果をヌル文字区切りで出力し、それをxargs -0で安全に受け取ってls -lを実行、その結果をファイルにリダイレクトしています。-print0xargs -0はセットで使うことで、ファイル名に空白や特殊文字が含まれていても安全に処理できます (pubs.opengroup.org)。GNU版findでは-exec <cmd> {} +でも同様に一括処理できますが、xargsはパイプラインで他のコマンドと組み合わせる場合に便利です。

一般的な利用例・ベストプラクティス

findコマンドはシステム管理や開発の現場で幅広く使われています。その中でも典型的なユースケースと、効率的かつ安全に利用するためのベストプラクティスをいくつか紹介します。

  • ログやバックアップファイルのクリーンアップ: 例えば「一定期間経過したファイルの削除/圧縮」はfindの代表的な用途です。

    • 古いファイル削除: find /path -type f -mtime +30 -exec rm {} \; – 30日以上前に更新されたファイルを削除。
    • 古いログ圧縮: find /var/log -name "*.log" -mtime +7 -exec gzip {} \; – 1週間以上前のログをgzip圧縮。
    • サイズでクリーンアップ: find /backup -type f -size +100M -exec rm {} \; – 100MB超の大きなファイルを削除してディスク空き容量確保、など。
      これらの操作は実行前に対象を確認することが重要です。一度削除すると元に戻せないため、まず-print-lsで対象一覧をチェックし、問題なければ-delete-exec rmに切り替えると安全です。
  • セキュリティチェック: システム上の怪しいファイルを探すのにも便利です。例えば:

    • SUID/SGIDファイル検出: find / -type f -perm -4000 -ls – SUIDビットが立っている可能性のある実行ファイルを一覧表示。
    • ワールド書き込み可能ファイル: find /path -perm -002 -not -type l -print – その他ユーザに書き込み許可があるファイルを検索(シンボリックリンクは除外)。共有ディレクトリなどで意図せず権限が広がっていないかチェックできます。
    • 孤立ファイル検出: find /home -nouser -o -nogroup -print – システム上に存在しないUID/GIDのファイルを検出(ユーザ削除漏れなどの管理目的)。
  • 開発時のコード検索: grepではなくfindでファイルリストを絞り込んでから処理する場面は多いです。

    • 拡張子で検索: find src/ -name "*.java" -print – Javaソースファイルだけリストアップ。
    • 複数パターン: find . \( -name "*.c" -o -name "*.h" \) -not -path "./vendor/*" -print – Cのソースとヘッダファイルを、サードパーティのvendorディレクトリを除外して検索。
    • 特定サイズ以上の画像: find . -type f -name "*.png" -size +5M -print – 大きな画像ファイルを探して最適化対象にする、など。
    • 内容との組み合わせ: 単純にファイル内容を検索するならgrep -Rで十分ですが、例えば「特定ディレクトリを除外してキーワード検索したい」場合にはfindで絞ってから-exec grepする手があります。例:
      find . -type f -not -path "./node_modules/*" -exec grep -H "TODO" {} \;
      
      これはnode_modulesを除外しつつ全ファイルから"TODO"を含む行を検索します。grep -R --exclude-dir=node_modules "TODO" .と同等の処理ですが、findのほうが細かい条件を追加しやすい場合に有用です。
  • 検索結果のフォーマット出力: ファイル一覧をスクリプトで処理したり、別ツールに渡す場合、-printfで情報を整形できます。

    • 例: ファイルパスとサイズのCSV出力:
      find /var/log -type f -printf "%p,%s\n" > file_sizes.csv
      
      これで各ログファイルのパスとサイズ(バイト)がカンマ区切りで出力され、Excel等で集計することもできます。
    • 例: HTMLファイルの最終更新日時一覧:
      find /var/www -name "*.html" -printf "%f was updated on %TD at %TT\n"
      
      %T書式を使い、各HTMLファイルの名前と更新日をわかりやすく表示しています。
      -printfは強力ですが前述の通り非標準ですので、他環境で動かす可能性があるスクリプトでは、代わりに-exec stat-exec lsを組み合わせる方法も検討しましょう。
  • パフォーマンス: findは強力ですが、広範囲(例えばルート直下/)を検索すると非常に多くのファイルを舐めるため時間がかかります。効率化のポイント:

    • 検索ディレクトリを絞る: 探したい領域が分かっているなら最初のパスで限定しましょう。例えばfind /home/user/Documents ...の方がfind / ...全体から絞るより無駄がありません。
    • 条件の順序: findの式は左から順に評価され、-aの場合は途中で偽になればその後の条件を評価しません。したがって、コストの低い判定(ファイル種別のチェックなど)や早期に偽になりやすい条件を先に書くと若干効率が上がります。例: -type f -name "*.mp4"と書く方が-name "*.mp4" -type fよりわずかに効率的です(前者はディレクトリに対し.mp4パターンチェックをする前に-type fで弾ける)。体感差は小さいですが、大量のファイルでは積み重なるため意識すると良いでしょう。
    • 不要領域の除外: -pruneで不要なディレクトリを完全に除外するのは大きな効果があります。特に/直下を検索する場合、/proc/sysなど仮想ファイルシステム、不要なマウントポイントは-xdevで跨らないようにするか、-path-pruneで除外してください。
    • locateとの使い分け: ファイル名だけで最近の更新は問わない、といった用途ならupdatedbコマンドで作成されたDBを引くlocateの方が一瞬で結果が出ます。ただし内容や詳細条件には対応できませんので、使い分けが肝心です(locateはcron更新のため最新ファイルはヒットしないこともあります)。
  • 出力結果の取り扱い: findの出力は基本的に1行=1ファイルパスです。しかし、ファイル名に改行が含まれている特殊ケースでは-printの改行区切りが破綻します。そうしたケースでは-print0とヌル区切りを使い、パイプ先で確実に処理する(xargs -0やPerl/Pythonスクリプト側でヌル区切りを読む)必要があります (pubs.opengroup.org)。また、シェルで直接処理する場合はIFSを調整した上でreadループで受け取る方法もあります (pubs.opengroup.org)。基本的にファイルリストを別コマンドに渡す場合はスペースや改行を含み得ることを常に念頭に置きましょう。この点で、件数が多くなければ-exec <cmd> {} \;-exec ... +を使う方が安全です(findが直接コマンドを呼ぶため、途中で中断した場合の部分的な出力の扱いなども含めてパイプより堅牢です (pubs.opengroup.org))。

  • その他の注意点:

    • パスの指定: 最初のパス引数に-で始まるもの(例えばディレクトリ名が-dataなど)は、findにオプションと誤認される可能性があります (pubs.opengroup.org)。そのような場合は明示的に./-data./経由で指定するか、find -- -data ...のように--(以降は全てパスとみなす)を使ってエスケープしましょう。
    • 現在のディレクトリ: パスを省略するとGNU find等では暗黙的に.が指定されたものとして動作しますが、POSIX標準上は未定義の挙動です (pubs.opengroup.org)。スクリプト内では可読性のためにも必ず検索開始ディレクトリを明記する習慣をつけましょう。
    • クォート漏れ: findのパターン引数(-name-pathのパターン)は、シェルが先にワイルドカード展開してしまうと意図通りに動作しません。例えばカレントに*.txtファイルが存在する状態でfind . -name *.txtと書くと、一度シェルが*.txtを展開してしまい結果がおかしくなります(find . -name file1.txt subdir/a.txt ...のように展開されエラーになる)。必ずfind . -name "*.txt"のようにクォートするか、特殊文字をエスケープしてください。
    • -deleteの位置: 先述しましたが、-deleteは表面上他の条件と並列に書けても必ず最後に置くようにしてください (man.freebsd.org)。例えばfind dir -type f -deleteは安全ですが、find dir -delete -type fは全てのdir直下を削除しようとしてから-type fを判定する挙動になり、ディレクトリも削除しようとしてしまいます。
    • -pruneの書き方: 一見find . -prune -o -name "*.txt"のように書けば除外できそうですが、これだと常に真を返す-pruneが先に評価され全ファイルが対象になってしまいます。-pruneを使う際は必ず**<条件> -prune -o <他の条件>という形で、-pruneが真になったときだけORの短絡評価で後続をスキップするよう配置してください(上記7**の例参照)。これも-a-oの優先順位を踏まえ、必要なら全体をカッコで囲んで思った通りの論理になるか確認しましょう。
    • 実装差: GNU findとBSD findではオプション名・挙動に微細な違いがあります。例えばBSD系(macOS含む)では-maxdepth等もサポートしますが、GNU拡張の-printfは利用できません。また、macOSのfindは-Eオプションで正規表現をBRE/EREから切り替える仕組みがありますが、GNU findは-regextypeで複数種から選べます。このように移植性が必要なスクリプトを書く際にはPOSIXにある機能(基本的な-name-type等)のみに留め、拡張が必要な場合は動的に判別したり代替コマンド(例: perlgrep等)を検討するのも手です。

練習問題

最後に、findコマンドの理解度を深めるための演習問題を用意しました。難易度は初級から上級まで混ざっています。各問題のあとに解答例と解説が続きますので、まず自分で考えてから解答を確認してください。

  1. 特定の拡張子を持つファイルの検索: /var/logディレクトリ以下から拡張子が.logのファイルをすべて見つけてフルパスを表示するfindコマンドを書いてください。
  2. 空のファイル・ディレクトリの検出: カレントディレクトリから、サイズが0バイトのファイルまたは中身が空のディレクトリを全て探し出すfindコマンドを書いてください。
  3. 期間とサイズを組み合わせた検索: /homeディレクトリ以下から「最終修正が7日以内で、サイズが1MiB(1メガバイト)以上」の通常ファイルを探し出し、長い形式のリスト(パーミッションやサイズなども表示)で結果を表示するコマンドを書いてください。
  4. ディレクトリを除外してコアファイルを検索: 現在のディレクトリ以下で、名前がcoreというファイル(サイズ0のコアダンプファイルを想定)を探すコマンドを書いてください。ただし、バージョン管理用の.gitディレクトリ内は検索しないよう除外しなさい。また、見つかったファイルはパスを表示するだけでよいものとします。
  5. 古いログファイルの圧縮: カレントディレクトリ以下にある.log拡張子のファイルのうち、最終更新が7日より古いものをすべてgzip圧縮(拡張子.gzに変更)するfindコマンドを書いてください(圧縮コマンドのgzipを利用し、findのオプションだけで実現してください)。
  6. 壊れたシンボリックリンクの検出: /homeディレクトリ以下から、宛先が存在しないシンボリックリンク(いわゆる「壊れたリンク」)を探し、そのパスを表示するfindコマンドを書いてください。

解答と解説

  1. 拡張子.logファイルの検索:
    解答例: find /var/log -type f -name "*.log" -print
    解説: /var/log以下を再帰的に探索し、通常ファイル(-type f)かつ名前が.logで終わるもの(-name "*.log")を表示します。-printは省略可能ですが、明示しても構いません。クォート付き"*.log"とすることで、シェルがワイルドカードを展開するのを防いでいます。

  2. 空ファイル・空ディレクトリの検出:
    解答例: find . \( -type f -size 0 \) -o \( -type d -empty \) -print
    解説: カレントディレクトリ(.)以下でサイズ0バイトのファイル(-type f -size 0)または中身が空のディレクトリ(-type d -empty)を探しています。-emptyはディレクトリの場合「中に子要素がない」ことを意味し、ファイルの場合はサイズ0と同義です。この問題では両方を区別して条件を書いてもよいですが、実は簡単にfind . -empty -printとするだけで「空のファイルまたは空のディレクトリ」の両方にマッチします(-emptyはファイル・ディレクトリどちらにも適用可能なため)。論理演算を使った上記解答例では、-emptyをディレクトリに限定するために-type dと組み合わせていますが、省略も可能です。

  3. 期間+サイズの複合検索:
    解答例: find /home -type f -mtime -7 -size +1M -exec ls -l {} \;
    解説: /home以下の通常ファイルで、過去7日以内に更新され-mtime -7)、サイズが1MiBを超える-size +1M)ものに対し、ls -lを実行しています。-exec ls -l {} \;部分で長い形式のリスト出力となり、パーミッションやサイズ、更新日時も確認できます。-size +1MはGNU/BSD拡張(Mは1048576バイト)ですが、POSIX環境しかない場合は-size +2047cと書く方法もあります(512バイトのブロック単位で2047以上=1MB超を意味)。このように、ファイル特性の条件を複数ANDで繋げば複合的な検索ができます。

  4. ディレクトリ除外とコアファイル検索:
    解答例: find . -path "./.git" -prune -o -type f -name "core" -size 0 -print
    解説: カレントディレクトリから、まず.gitディレクトリを-pruneで除外し、その他の部分で名前がcoreかつサイズ0バイトの通常ファイルを検索しています。問題文では「coreファイル(サイズ0のもの)」とあったので-size 0も条件に含めています(不要なら省略可)。-pruneの使い方がポイントで、-path "./.git" -prune -o ...とすることで.git内は探索しません。後半の-type f -name "core" -size 0がORによって.git以外の場所に適用され、該当ファイルがあれば表示(-print)されます。

  5. 古いログファイルの圧縮:
    解答例: find . -type f -name "*.log" -mtime +7 -exec gzip {} \;
    解説: カレントディレクトリ以下の7日より更新が古いログファイルを見つけ、gzipコマンドで圧縮しています。gzipは実行すると元のファイルを.gzに置き換えるため、この1行で圧縮が完了します。-exec ... \;は見つかったファイルごとに1回ずつgzipを実行します。ファイル数が多い場合は-exec ... +でまとめて処理することもできますが、gzipは複数ファイルを引数に取ると一つずつ個別に圧縮するだけなので、振る舞いは同じになります(大量のファイルを並列処理したい場合は別途高度な方法が必要です)。

  6. 壊れたシンボリックリンクの検出:
    解答例: find /home -xtype l -print
    解説: GNUまたはBSD系のfindを想定した回答です(どちらも-xtype lサポートあり)。/home以下で、リンク先を辿った型がl(シンボリックリンク)であるもの、すなわちリンク先が存在しないシンボリックリンクを検索して表示します。リンク先が存在する場合、-xtypeはその実体の型(fdなど)になるため除外され、壊れているリンクのみが結果に残ります (unix.stackexchange.com)。環境によっては-xtypeが使えない場合もあるため、移植性を考えるなら以下のような代替策もあります:

    find /home -type l ! -exec test -e {} \; -print
    

    これは「シンボリックリンクで、かつtest -e(存在チェック)コマンドを実行した結果が偽だったもの」を探す方法です。! -exec ...の部分で「リンク先が存在しない」判定を行っています。どの方式でも目的は達せられますが、GNU環境では簡潔な-xtype lを覚えておくと便利です。

o3 Deep Research·today at 9:19 am