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:
- Core functionality and purpose
- All options and features (except extremely obscure ones), with clear descriptions
- Examples that cover every single option or feature you provided above, mixing basic to advanced, each with expected output or behavior
- Common use cases and best practices
- 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.
承知しました。 今後は、各シェルコマンドやトピックについて、以下の内容を盛り込んだ包括的な解説を行います:
-
コアな機能と目的
-
主なオプションや機能(極端にマイナーなものを除き、全て網羅して明確に説明)
-
具体例(上記の全オプションや機能を網羅する形で、基本的な使い方から上級者向けテクニックまで示し、それぞれ期待される出力や動作も記載)
-
一般的な使用例とベストプラクティス(実務での活用方法や効率的な使い方の提案)
-
注意点や落とし穴(誤用しやすい点や陥りがちなミス、その回避策)
また、説明内ではPOSIX標準外の機能には [Non-POSIX]
と明記し、シェル固有の機能(例:[bash]
, [zsh]
など)は該当シェル名をタグとして付け加えます。GNU/BSD、BusyBoxやプラットフォーム([Linux], [macOS] 等)の実装差がある場合も適宜注釈し、明確に区別します。
最後に、各トピックに関して少なくとも5問の練習問題(難易度は初級から上級まで)を作成し、その解答と解説を回答の末尾に示します。これらの回答は全て一つのメッセージ内で完結するようにし、他の資料を参照しなくても十分理解できる内容を目指します。今後ともよろしくお願いいたします。
find
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.txt
やFile.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
ではそれぞれf
やd
等と判定されるため除外されます) (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)になります。単独指定時はこの完全一致判定になります。
- 数字は8進数表記で指定します(先頭に
<mode>
の前に**-
(ハイフン)**を付けると、「指定したビットが最低限すべてセットされている」場合に真となります (pubs.opengroup.org)。これは一種の「包含」条件です(モードのその他のビットは問わない)。- 数字例:
-perm -4000
はSUIDビットが設定されているファイルをすべて検索します(他のビットは自由)。 - 記号例:
-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 <日時文字列>
のように使うと現在時刻ではなく指定した日時(Y
がt
の場合は基準が指定日時となる)と比較することもできます。この機能を使えば「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
によりリンク先を評価した上でタイプを判定するため、リンク先が存在するlink
やextlink
はそれぞれ通常ファイルとして扱われ-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 0
は512バイトブロック数で0という意味なので、実質サイズ0バイトのファイルを指します(512バイト未満は切り上げて1ブロックになるため、0ブロック=0バイト)。結果として空のb.sh
とfile1.txt
が見つかりました。
# サイズが5バイトを超えるファイルを検索(単位指定の例)
$ find testdir -type f -size +5c -print
testdir/subdir/a.txt
testdir/skipme/secret.txt
-size +5c
は5バイトより大きい(+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 -1
は24時間以内に修正されたファイルを指し、直近で内容が変更・作成されたファイル全てが表示されています。
# 7日より前に変更されたファイルを検索
$ find testdir -mtime +7 -print
testdir/file1.txt
(file1.txt
は意図的に更新日時を過去日に設定したものとします。)-mtime +7
は8日以上前に修正されたファイルで、この例では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
を実行、その結果をファイルにリダイレクトしています。-print0
とxargs -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のファイルを検出(ユーザ削除漏れなどの管理目的)。
- SUID/SGIDファイル検出:
-
開発時のコード検索:
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出力:
これで各ログファイルのパスとサイズ(バイト)がカンマ区切りで出力され、Excel等で集計することもできます。find /var/log -type f -printf "%p,%s\n" > file_sizes.csv
- 例: HTMLファイルの最終更新日時一覧:
find /var/www -name "*.html" -printf "%f was updated on %TD at %TT\n"
%T
書式を使い、各HTMLファイルの名前と更新日をわかりやすく表示しています。
-printf
は強力ですが前述の通り非標準ですので、他環境で動かす可能性があるスクリプトでは、代わりに-exec stat
や-exec ls
を組み合わせる方法も検討しましょう。
- 例: ファイルパスとサイズのCSV出力:
-
パフォーマンス:
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
等)のみに留め、拡張が必要な場合は動的に判別したり代替コマンド(例:perl
やgrep
等)を検討するのも手です。
- パスの指定: 最初のパス引数に
練習問題
最後に、find
コマンドの理解度を深めるための演習問題を用意しました。難易度は初級から上級まで混ざっています。各問題のあとに解答例と解説が続きますので、まず自分で考えてから解答を確認してください。
- 特定の拡張子を持つファイルの検索:
/var/log
ディレクトリ以下から拡張子が.log
のファイルをすべて見つけてフルパスを表示するfind
コマンドを書いてください。 - 空のファイル・ディレクトリの検出: カレントディレクトリから、サイズが0バイトのファイルまたは中身が空のディレクトリを全て探し出す
find
コマンドを書いてください。 - 期間とサイズを組み合わせた検索:
/home
ディレクトリ以下から「最終修正が7日以内で、サイズが1MiB(1メガバイト)以上」の通常ファイルを探し出し、長い形式のリスト(パーミッションやサイズなども表示)で結果を表示するコマンドを書いてください。 - ディレクトリを除外してコアファイルを検索: 現在のディレクトリ以下で、名前が
core
というファイル(サイズ0のコアダンプファイルを想定)を探すコマンドを書いてください。ただし、バージョン管理用の.git
ディレクトリ内は検索しないよう除外しなさい。また、見つかったファイルはパスを表示するだけでよいものとします。 - 古いログファイルの圧縮: カレントディレクトリ以下にある
.log
拡張子のファイルのうち、最終更新が7日より古いものをすべてgzip圧縮(拡張子.gz
に変更)するfind
コマンドを書いてください(圧縮コマンドのgzip
を利用し、find
のオプションだけで実現してください)。 - 壊れたシンボリックリンクの検出:
/home
ディレクトリ以下から、宛先が存在しないシンボリックリンク(いわゆる「壊れたリンク」)を探し、そのパスを表示するfind
コマンドを書いてください。
解答と解説
-
拡張子
.log
ファイルの検索:
解答例:find /var/log -type f -name "*.log" -print
解説:/var/log
以下を再帰的に探索し、通常ファイル(-type f
)かつ名前が.log
で終わるもの(-name "*.log"
)を表示します。-print
は省略可能ですが、明示しても構いません。クォート付き"*.log"
とすることで、シェルがワイルドカードを展開するのを防いでいます。 -
空ファイル・空ディレクトリの検出:
解答例: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
と組み合わせていますが、省略も可能です。 -
期間+サイズの複合検索:
解答例: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で繋げば複合的な検索ができます。 -
ディレクトリ除外とコアファイル検索:
解答例: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
)されます。 -
古いログファイルの圧縮:
解答例:find . -type f -name "*.log" -mtime +7 -exec gzip {} \;
解説: カレントディレクトリ以下の7日より更新が古いログファイルを見つけ、gzip
コマンドで圧縮しています。gzip
は実行すると元のファイルを.gz
に置き換えるため、この1行で圧縮が完了します。-exec ... \;
は見つかったファイルごとに1回ずつgzip
を実行します。ファイル数が多い場合は-exec ... +
でまとめて処理することもできますが、gzip
は複数ファイルを引数に取ると一つずつ個別に圧縮するだけなので、振る舞いは同じになります(大量のファイルを並列処理したい場合は別途高度な方法が必要です)。 -
壊れたシンボリックリンクの検出:
解答例:find /home -xtype l -print
解説: GNUまたはBSD系のfindを想定した回答です(どちらも-xtype l
サポートあり)。/home
以下で、リンク先を辿った型がl
(シンボリックリンク)であるもの、すなわちリンク先が存在しないシンボリックリンクを検索して表示します。リンク先が存在する場合、-xtype
はその実体の型(f
やd
など)になるため除外され、壊れているリンクのみが結果に残ります (unix.stackexchange.com)。環境によっては-xtype
が使えない場合もあるため、移植性を考えるなら以下のような代替策もあります:find /home -type l ! -exec test -e {} \; -print
これは「シンボリックリンクで、かつ
test -e
(存在チェック)コマンドを実行した結果が偽だったもの」を探す方法です。! -exec ...
の部分で「リンク先が存在しない」判定を行っています。どの方式でも目的は達せられますが、GNU環境では簡潔な-xtype l
を覚えておくと便利です。