findコマンドの使い方を簡単に理解するための7つのルール＋実践的な知識

## はじめに

`find` コマンドの使い方は、ざっくり調べただけではよくわからんとなりますが、見逃しがちなルールを知れば簡単に理解できます。`find` コマンドに限りませんが使い方を調べるのが面倒だからと**曖昧な理解で使うと逆にもっと分からなくなって時間がかかります**。急がば回れ、理解して正しく使ったほうがシンプルで楽で簡単です。この記事では `find` コマンドの使い方を理解するために必要なルールと使い方の実践的な知識をまとめました。

**Q&A（？）:** `-type` や `-perm` の説明はしないの？ ⇒ それらはドキュメントを読むか検索すればすぐにわかることで難しいポイントではありません。重要なのは基本のルールを理解することです。

**関連記事**

- [POSIX 準拠のシェルスクリプトでは find | xargs よりも find -exec {} + を使うべき！](https://qiita.com/ko1nksm/items/9ff1f212255e8520070c)
- 移植性の話はこちら ⇒ [findコマンドのオプション･評価式の一覧と移植性のまとめ](https://qiita.com/ko1nksm/items/32522cc83a2fb170c18c)
- [xargs 完全理解マニュアル - xargs は拡張引数 (extended arguments) の略って知っていますか？](https://qiita.com/ko1nksm/items/34cd96d39c2c5cd80eb4)

:::note warn
興味がある人への「find コマンドの設計の歴史」の紹介
[The History of the Design of Unix’s Find Command](https://doc.cat-v.org/unix/find-history)

Dennis Ritchieが find コマンドはどうしてこうなった？の質問に答えています。
（もっとも結論は「わからん」なわけですが）
:::

## ルール

`find` コマンドの検索のルールは省略されている部分を補完してやれば、多くのプログラマにとって馴染みが深いものです。すでに今持っている知識を活用することが出来ます。

### ルール1 find コマンドは指定したパスから検索する

`find` コマンドは引数で指定したパスから検索します。`find` コマンドの構文は次のようになっています。

```txt:検索パスから検索する
find [コマンドラインオプション] 検索パス... [評価式]
```

検索パスは複数指定可能です。ファイルを指定することも出来ますがファイルを指定しても検索の意味はあまりないので通常はディレクトリを指定します。このディレクトリの中からファイルやディレクトリをすべて見つけ出すというのが `find` コマンドの最も簡単な使い方です。紛らわしいのは GNU 版の `find` コマンドでは検索パスを省略できるという点です。検索パスを省略した場合はカレントディレクトリ (`.`) を指定したのと同じ意味になります。移植性がない（macOS などで使えない）ので検索パスは省略しないことをおすすめします。検索パスがあると、どこからファイルやディレクトリを検索しているのかが明確となり、コマンドラインオプションと評価式の区別が付きやすくなります。

ちなみに、次のような書き方は `find` コマンドを使う意味はありません。この後に評価式を付け加えれば、全く意味がないというわけではないのですが、シェルの機能によってパス名 (`*.txt`) を展開しており、つまりこの場合はファイルを検索しているのはシェルです。

```sh
# file1.txt file2.txt file3.txt というファイルがある状態で以下を実行すると・・・
find *.txt

# シェルが以下のファイル名に展開してから find コマンドを実行する（find コマンドは検索していない）
find file1.txt file2.txt file3.txt

# find コマンドの機能で検索する場合の書き方
find . -name '*.txt' # クォートはほぼ必須（ *.txt のようにクォートなしで書くのは間違い）
```

### ルール2 検索パスの後ろはコマンドラインオプションではなく評価式

`find` コマンドの検索パスの後ろには評価式を書きます。**評価式はコマンドラインオプションではありません**。コマンドラインオプションは検索パスの前に書くものです。

```txt:検索パスの後ろは評価式
find [コマンドラインオプション] 検索パス... [評価式]
```

評価式は数式やプログラミング言語の式と同じようなものです。`A and (B or C)` や `foo() && ( bar() || baz() )` と同じようなものだと考えます。評価式は要素式（`-name` など）と演算子（`-a`など）の組み合わせです。

**補足** 要素式は一般的な訳ではないかもしれませんが primary（？）やpredicate（述語）も微妙なのでそれよりはマシだと考えています。

紛らわしいのは GNU 版の `find` コマンドでは検索パスを省略できるという点です。検索パスを省略してしまうとコマンドラインオプションと評価式の区別が付きづらくなってしまいます。コマンドラインオプションは `-H` や `-L` のようなものです。珍しく GNU find にはハイフン二つで始まるロングオプションはないようなので区別するのは簡単でしょう。

ロングオプションはそもそも GNU コマンドの拡張オプション形式なので伝統的な Unix コマンドにはありません。昔からある `find` コマンドが `-name` のような「ロングオプション」をサポートしているわけがないのです。かと言ってこれはショートオプションでもありません。`-name` のようなハイフン一つで始まるロングオプションのようなものは評価式を構成する要素式です。

**補足2** ショートオプションと紛らわしい要素式に `-d` がありますが、これは `-depth` と同じで意味で `-d` は非推奨となっています。`-d` は使わないようにすれば問題ありません。後は演算子の `-a` と `-o` もショートオプションと同じ形式ですが評価式の最初に書くことはないので紛らわしいということはないでしょう。

### ルール3 評価式は検索対象ではなく絞り込み条件

`find` コマンドの評価式は絞り込み条件です。検索対象ではありません。SQL で言えば WHERE に相当するものです。検索パスは FROM で指定するテーブルに相当します。

```txt:評価式は絞り込み条件
find [コマンドラインオプション] 検索パス... [評価式]
```

よく `-name '*.txt'` でテキストファイルを「検索する」とか `-type f` でファイルのみを「検索する」とか言いますが、検索パスで指定したパスから見つけ出したファイルまたはディレクトリを`-name '*.txt'` や `-type f` という条件で絞り込むと言ったほうが適切です。

これを（検索パスを省略した状態で）`-name '*.txt'` と `-type f` に当てはまるものを検索すると読んでしまうと、カッコや `-o`が登場してさらに複雑になった時に混乱します。

### ルール4 演算子を省略した時はAND結合

演算子を省略した場合、要素式の間には AND (`-a`) が指定されているものとして扱われます。例えば以下の二つは同じ意味です。

```sh:演算子を省略したらAND結合
-name '*.txt' -type f

-name '*.txt' -a -type f

# 名前が '*.txt' AND タイプがファイル
```

`-exec` を使う場合でも同じです。

```sh:-execもAND結合されている
-name '*.txt' -type f -exec touch {} +

-name '*.txt' -a -type f -a -exec touch {} +

# 名前が '*.txt' AND タイプがファイル AND touchコマンド実行
```

数式で `a × b` を `ab` と表記するのと同じような扱いなのでしょうが、`-a` の省略によってコマンドラインオプションの羅列のように見えてしまうため混乱してしまいます。これらは実は AND でつながった要素式なのです。

### ルール5 演算子は優先順位があるショートカット演算子

評価式は数式やプログラミング言語の式と同じように優先順位があります。優先順位は高い順に `()`、`!`、`-a`、`-o` です。一般的な優先順位ですが AND と OR の優先順位はそれほど知られていない気がするので、`-o` が含まれるときはカッコを付けたほうがよいと思います。ちなみに `!` の代わりに `-not`、`-a` の代わりに `-and`、`-o` の代わりに `-or` と書けますが、Solaris や AIX では対応しておらず POSIX で標準化されていません。

また多くのプログラミング言語の式と同じように演算子はショートカット演算子になっています。ショートカット演算子とは評価が確定した段階で残りの評価を打ち切ることです。（結果がわかっているので評価する意味がない）

```txt:ショートカット演算子とは
A and B ・・・ A が偽なら最終的な結果が偽であることは確定しているので B は実行しない
A or B ・・・ A が真なら最終的な結果が真であることは確定しているので B は実行しない
```

`find` コマンドの動きはこのショートカット演算子の動きで構成されています。

```sh:findコマンドの動きはショートカット演算子
find . -name '*.txt' -type f -exec touch {} +
# A (-name '*.txt') and B (-type f) and C (-exec touch {} +)
# A が真なら B を実行する。そうでないなら残りは実行しない。
# B が真なら C を実行する。そうでないなら C は実行しない。
```

### ルール6 全ての要素式は真偽値を返す

全ての要素式は真偽値を返します。`-depth` であっても `-exec` であっても要素式なので真偽値を返します。`-depth` （ディレクトリそのものよりもディレクトリの中身を先に処理する）は常に真を返します。`-exec {} \;` はコマンドの終了ステータスが 0 の場合に真を返します。ただし `-exec {} +` の場合はコマンドの終了ステータスが 0 以外でも真を返します。`find` コマンドのドキュメント（`man find`）には何を返すのかがちゃんと書かれているので参照してください。

なお要素式の真偽値は `find` コマンド全体の終了ステータスと直接の関係はありません。`find` コマンド全体の終了ステータスは全ての処理が正常終了した場合に 0 を返します。例えば `-exec {} +` は常に真を返しますが、実行したコマンドの終了ステータスが 0 以外を返していれば、`find` コマンド全体の終了ステータスはエラー（0以外）です。ちなみに `exec {} +` が常に真を返す理由は複数のファイルをまとめて実行するからです。ファイル一つ一つの評価値を知る方法がないので真にするしかありません。

### ルール7 アクションを省略した時は -print

 アクションとは副作用を持つ要素式です。つまり真偽値を返す以外の処理を行う要素式のことで、具体的には `-exec` や `-delete` などのことです。アクションを省略した場合、全体を括弧で括って `-print` を AND でつなげのと同じ意味になります。


```sh:アクション省略時はデフォルトで-printが使われる
-name '*.txt' -type f

'(' -name '*.txt' -a -type f ')' -a -print
```

昔は（他のアクションを書いていないのに）`-print` を書かないと特定の条件の場合に出力されないバグがあったらしく省略しない方が良いと言われていたようですが、現在は `-print` を書く必要はないはずです。

## 実践的な知識

`find` コマンドの基本を理解したら、次は実践的な知識です。

### -exec は {} にファイル名を差し込んで呼び出しているだけ

「`-exec コマンド {} \;`」や「`-exec コマンド {} +`」の意味がよくわからないと言っている人が多いようですが、単に `{}` の部分に見つけたファイル名を差し込んでコマンドを呼び出しているだけです。

```sh:{}に見つけたファイル名を差し込んでコマンドを呼び出す
find . -name '*.txt' -exec touch {} \;
# \; は見つけた複数のファイルを一つづつ差し込む
# touch <見つけたファイル名1>
# touch <見つけたファイル名2>
# touch <見つけたファイル名3>
#   ︙

find . -name '*.txt' -exec touch {} +
# + は見つけた複数のファイルをまとめて差し込む
# touch <見つけたファイル名1> <見つけたファイル名2> <見つけたファイル名3> ...
```

後ろの `\;` や `+` は `-exec` の終了のマークです。`-exec` も要素式のひとつなのでその後に別の要素式をつなげることが出来ます。その際にどこまでをコマンドに渡す引数なのか、つまり区切りはどこのなのか？を示すマークが必要です。`-exec` から `\;` または `+` の中がコマンド部分で `{}` にファイル名を差し込んでコマンドを実行しているだけです。

```sh:空ファイル以外を出力
# 区切り記号がないとどこまでが -exec で実行するコマンドにわたす引数なのか区別できない
find . -name '*.txt' -exec test -s {} \; -print
find . -name '*.txt' -exec [ -s {} ] \; -print # 上記の別の書き方

# 以下と同等
# find . -name '*.txt' ! -empty -print
```

### 「-exec ... {} \\;」は「-exec ... {} ';'」と同じ意味

シェル言語の文法を正しく理解している人ならすぐにわかることですが、最初は `\;` ってなんだ？って思うのではないでしょうか。これは `;` という文字を引数として渡したいけど、`;` がシェルのメタ文字なので `';'` と書く・・・代わりに `\` でエスケープして文字列にしているだけです。

```sh:-exec {} \\; と -exec {} ';' は同じ意味
find . -name '*.txt' -exec touch {} \;
find . -name '*.txt' -exec touch {} ';'
```

`(` と `)` も同様にシェルのメタ文字なので、文字列として渡すには `'('` や `')'` と書かないといけないのですが `\(` や `\)` でも構いません。個人的にはシェル特有のエスケープを使った書き方よりも、多くの言語で一般的なクォートを使うほうがわかりやすいと思うのですが、よく見かける例ではエスケープが使われています。

### -name などの引数はクォートが必要

`-name` などの引数もシェルのメタ文字（`*` など）が含まれる場合はクォートが必要です。そうしないと先にシェルが（ファイルが見つかれば）ファイル名に展開してしまいます。パターンにマッチするかどうかはシェルではなく `find` コマンドが判断しています。

```sh:引数はクォートすること
find . -name *.txt    # だめな書き方
find . -name '*.txt'  # クォートが必要
```

なお検索パスの場合は（シェルに展開させたい場合は）クォートは必要ありません。検索パスを複数指定したいことはあまりなさそうではありますが。

```sh:検索パスはクォートしない
find /tmp/dir* -name '*.txt'
# /tmp/dir1 /tmp/dir2 のようなパスが存在していれば
# シェルのパス名展開の機能によってそれらのパスに展開される
```

### find コマンド出力のファイルリストを grep コマンドで絞り込まない

ファイル名の絞り込みには `-name` や `-path` を使います。これらは POSIX で標準化されています。正規表現ではなく GLOB パターンしか使えませんが通常のユースケースでは十分のはずです。（普通はありませんが）ファイル名に改行が含まれていても正しく動きますし、正規表現に関する罠もなく短くてわかりやすいですね。テキスト処理のための `grep` コマンドを違う用途に使い回すのではなく、ファイル検索はファイル検索の専門家に任せましょうということです。どうしても正規表現での絞り込みを使いたい場合は POSIX で標準化されていませんが `-regex` も使えます。`-regex` は GNU だけではなく macOS や FreeBSD でも対応済みです。

```sh:grepで絞り込まない 
find . -name '*.sh'   # こちらを使う

find . | grep '\.sh$' # grepで絞り込まない
find . | grep '.sh'   # 間違った正規表現（fish.jpg というファイル名にマッチしてしまう）
```

対話シェルで雑にやる場合は `grep` コマンドを使って絞り込んでも構いません。間違って書いた時に出力を目視で確認して何かがおかしいことに気付けるのであれば。

### xargsから-execへの置き換えは簡単で短い

`xargs` を書く代わりに `-exec` を書くだけです。

```sh
# ❌ファイル名に空白やシングル・ダブルクォーテーションが含まれると正常に動かない
find . -name '*.txt' | xargs touch

# find | xargs の正しい書き方（POSIX Issue8 準拠）
find . -name '*.txt' -print0 | xargs -0 touch

# -execの方が短い（| xargsを取り除いて -exec と {} + を付け加えるだけ）
find . -name '*.txt' -exec touch {} +
```

### find コマンドを xargs コマンドと組み合わせない

`find` コマンドはファイルを検索したファイルに対して任意のコマンドを実行するコマンドです。「任意のコマンド」を省略すると `-print`（ファイル名を画面に出力）がデフォルトで使われるというだけです。ファイル名を画面に出力する代わりに任意のコマンドを実行する場合は `-exec` を使います。

`xargs` と組み合わせた場合の問題の一つが、ファイルが見つからない場合でもコマンドが実行されてしまうことです。

```console:xargsと組み合わせるとファイルが見つからない場合にエラーになる
$ find . -name '*.tekisuto' | xargs touch
touch: missing file operand
Try 'touch --help' for more information.
```

これを防ぐために GNU 版 `xargs` には `-r` オプションがあり、POSIX Issue 8 でも標準化されますが Solaris や AIX ではまだ実装されていません。ちなみに BSD 版では元からデータがない場合はコマンドを呼び出さないという動作です。`xargs` の本来の機能は多すぎる引数を分割するコマンドなので、引数が 0 個の場合でもコマンドを呼び出すのが本来あるべき動作で `xargs` の昔からの動作です。ともかく `xargs` はファイルがない場合の処理が面倒ですが、`-exec` を使えばファイルがない場合はコマンドが呼び出されません。

組み合わせるコマンドが増えれば増えるほど物事は複雑になります。`find` コマンドから`xargs` 経由でコマンドを実行すると**間にいるだけの `xargs` の仕様に悩まされる**ことになります。`find` コマンドから直接コマンドを実行したほうが単純です。特に使う理由がないのであれば `xargs` ではなく `-exec` を使うようにしましょう。

**`xargs` と組み合わなければいけないとしたら並列処理の `-P` オプションを使うとき**ぐらいでしょう（[GNU Parallel](https://www.gnu.org/software/parallel/) の方が良いと思いますがインストールが必要です）。並列処理を使うときはコマンドの実行回数が増えてしまう（でないと並列処理できない）ので、こちらも正しく使う必要がありますが、その話はこの記事の対象ではないので割愛します。`xargs` はデータの流れが直列でコマンド実行も直列なので `-P` オプションを指定しないかぎり並列処理の効果はありません。

### 「-exec ... {} +」は「-exec ... {} \;」より圧倒的に速くてオススメ

`find` で `-exec` を使うよりも `xargs` の方が速いという主張を見かけますが、それは `-exec {} \;` との比較の場合です。`-exec {} +` は速く `xargs` とほとんど同じです。

なぜ `-exec {} +` が速いのかというと、ファイルごとにコマンドを呼び出すのではなく、複数のファイルをまとめてコマンドを呼び出す呼び出すからです。

```sh:-exec {} \\; は遅く -exec {} + は速い
find . -name '*.txt' -exec rm {} \;
# ファイルごとに rm コマンドが呼び出されるから遅い
# rm file1.txt; rm file2.txt; rm file3.txt; ...

find . -name '*.txt' -exec rm {} +
# 複数のファイルをまとめて rm コマンドを呼び出すから速い
# rm file1.txt file2.txt file3.txt ...
```

`xargs` が速いと言われていたのもこれと同じ理由で、複数のファイルをまとめてコマンドを呼び出すからです。まとめる数はコマンドライン引数の制限を超えない数で、`find -exec {} +` も同じように呼び出します。

:::note warn
**追記** 対象ファイルが多い場合は `xargs` と同じようにシステムの `ARG_MAX` を超えないように引数サイズを制限して呼び出すことは [POSIX find](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html) で規定された動作です。

> The size of any set of two or more pathnames shall be limited such that execution of the utility does not cause the system's {ARG_MAX} limit to be exceeded. If more than one argument containing the two characters "{}" is present, the behavior is unspecified.

[興味がある人への POSIX で `-exec ... {} +` が標準化されるに至るまでの経緯](https://collaboration.opengroup.org/external/pasc.org/interpretations/unofficial/db/p1003.2/pasc-1003.2-210.html)
<sub>注意「`-exec ... {} +`」はPOSIXが考え出した仕様ではなくSVR4等で実装済みの仕様を標準化したものです。</sub>
:::

**`xargs` も使い方を正しく知らなければ遅くなります**。例えば `-I` オプションや `-n 1` などを使うとファイルが多い時に劇的に遅くなります。これはファイルごとにコマンドを実行するためです。シェルスクリプトが遅くなる原因の一つが外部コマンドの実行回数です。外部コマンドの実行回数を意識すればシェルスクリプトは問題なるほど遅くはありません。

```sh:xargsの遅い書き方
find . -name '*.txt' -print0 | xargs -0 -I{} touch {}

find . -name '*.txt' -print0 | xargs -0 -n 1 touch
```

`xargs` の場合は `find ... -print0 | xargs -0 ...` と書かないとスペースやクォーテーションが含まれている時にエラーが発生しますが、`find -exec {} +` だとその問題がありません。`find -exec {} +` は POSIX で以前から標準化されていて移植性が高いというメリットもあります。ちなみに `find ... -print0 | xargs -0 ...` は近くに改定される POSIX Issue 8 で標準化されます。とはいえ AIX 以外ではすでに対応しているので移植性が低いというわけではありません。

### 「-exec ... {} +」に適合するコマンドを使う・作る

`-exec ... {} +` の効果をうまく引き出すには `cmd [OPTIONS] [ARGS] file...` のように引数の最後に複数のファイル名を渡せるようになっている必要があります。このスタイルに適合しないコマンドの例が `mv` コマンドです。`mv` コマンドは以下のような構文になっているため、複数のファイル名を渡せません。

```txt
mv [OPTIONS] ファイル名... 移動先ディレクトリ
```

しかし GNU 版では以下のような構文もサポートしており `-exec ... {} +` の使い方に適合しています。これが引数の順番を入れ替えただけの構文が用意されている理由です。

```txt
mv [OPTIONS] -t 移動先ディレクトリ ファイル名... 
```

上記の `mv` コマンドの使い方は複数のファイルを一つのディレクトリにまとめる使い方でしたが、拡張子を一括変換したい時に `mv` コマンドはうまく適合しません。そのような場合は代わりに `rename` コマンドを使うと良いでしょう。

```sh
# mv コマンドでは拡張子を変換する場合、変換元と変換先の二つの引数を
# 指定しなければいけないので、ファイル名... の形にできない
mv 変換元ファイル名 変換先ファイル名

# Debian 版の rename コマンド（正規表現でファイル名を変換できる）
rename -n 's/\.jpeg$/.jpg/' 変換元ファイル名... 
```

いずれ標準的なコマンドでは目的を達成できない時、別の言語でなにかコマンドを作ることになるでしょう。そのような場合、コマンドラインインターフェイスを複数のファイル名を受け付けるように作っておけば `-exec ... {} +` とうまく適合させられます（ちなみに `xargs` と適合させる場合も同様です）。`find` コマンドとうまく連携するように作っておけば、新しく作るコマンドにファイル検索機能を作る必要がなくなります。

### 文字列が含まれたファイルを検索する時は rg や ag などを使う

文字列が含まれたファイルを検索するのであれば `grep` よりも速くもっと優れたコマンドがあります。対話シェルでの利用ではこちらの方がオススメです。

- `rg` (ripgrep) - https://github.com/BurntSushi/ripgrep
- `ag` (the_silver_searcher) - https://github.com/ggreer/the_silver_searcher

POSIX で標準化されていませんが、`grep` の `-r` オプションを使えばディレクトリ内を検索することが出来るのでこれで十分な場合も多いはずです。シェルスクリプトで使う場合や `find` コマンドの柔軟な絞り込み機能を利用したい場合は `find` コマンド組み合わせると良いでしょう。

```sh:ファイルの中身を検索する例
# -exec {} + だと簡潔で速い
find . -name '*.txt' -exec grep "検索文字列" {} +

# xargs だと長い
find . -name '*.txt' -print0 | xargs -0 grep "検索文字列"
```

### BSD系で-mtimeの意味がGNU・macOS・Solarisと一日違う

**macOS を除く** BSD 系 OS（FreeBSD、NetBSD、OpenBSD）の `-mtime` の仕様は GNU、macOS、Solaris と異なります（おそらく POSIX に準拠していません→[理由](https://austingroupbugs.net/view.php?id=1259)）。GNU、macOS、Solarisでは `-mtime N` の意味は N 日前の「一日の範囲」で、N 日前からその前日の範囲を意味しています。`-mtime +N` と `-mtime -N` はその「一日の範囲」よりも過去または未来を意味するので `-mtime +3` は 4 日前よりも過去、`-mtime -3` は 3 日前よりも未来という意味になります。ちなみに1日とは86400分のことで、その日の 0 時を基準にするわけではありません（GNU find には 0 時を基準にする `-daystart` があります）。

```txt:GNU・macOS・Solars・AIX の解釈（POSIX 準拠）
                 4日前     3日前                       現在
--------|--------|--------|--------|--------|--------|--------|--------
================><=======><============================================
                 -mtime 3 の範囲
-mtime +3 の範囲           -mtime -3 の範囲
```

しかし BSD 系の OS では `-mtime N` の意味は N 日前の「一日の範囲」ですが、N 日前からその翌日の範囲で、`-mtime +N` と `-mtime -N` はその「一日の範囲」とは無関係に N 日前を意味し、`-mtime +3` は 3 日前よりも k過去、`-mtime -3` は 3 日前よりも未来という意味になります。

```txt:BSD 系 OS の解釈
                          3日前     2日前             現在
--------|--------|--------|--------|--------|--------|--------|--------
                          <=======>
                          -mtime 3 の範囲
=========================><============================================
        -mtime +3 の範囲   -mtime -3 の範囲
```

実害はほとんどないと思いますが厳密には GNU と macOS・Solaris も細かい動作の違いがあります。詳細は「[【find -mtimeに移植性はない】Unix標準コマンドを使ってる限り、どこでも動くシェルスクリプトなんて書けるわけねーだろ](https://qiita.com/ko1nksm/items/5e9539ec8eb8f66a0239)」を参照してください。

余談ですが分単位で指定する **`-mmin`（と類似する）要素式はすでに移植性があります**（GNU、macOS、BSD 系 OS、Solaris、AIX で実装済み）。POSIX で標準化されていないからといって使えないとする理由はありません。

### 厳密に日時を指定したい場合は -newer や -newerXY を使用する

`-newer` や `-newerXY` を使用するとファイルや指定した日時と比較することが出来ます。`-newer` は指定したファイルの更新日時としか比較できませんが POSIX で標準化されています。

任意の日時のファイルは `touch` コマンドで作成することが出来ます。

```sh:date コマンドで任意の日時のファイルを作成する方法（POSIX 準拠）
$ touch -d 2023-10-15T12:34:56
# -d の書式 YYYY-MM-DDThh:mm:SS[.frac][tz]
# T はスペースでも構わない

$ touch -r ファイル
# 指定したファイルの日時で作成する

$ touch -t 202310151234.56
# -t の書式 [[CC]YY]MMDDhhmm[.SS]
# 書式が分かりづらいので避けるのをオススメ
```

```sh:date コマンドで任意の日時のファイルを作成する方法（POSIX 準拠でない方法）
# GNU 拡張（date コマンドと同等の形式が利用できる）
$ touch -d 'yesterday'
```

`-newerXY` の `XY` は「XY」という文字列ではなく X と Y にはそれぞれ以下の文字が入り、指定したファイルの日時または指定した日時を比較することが出来ます。

- a ファイルのアクセス時間 (`-neweraa` は `-anewer` と同等）
- B ファイルの作成時間 (`-newerBB` は `-Bnewer` と同等）
- c ファイルの変更時間 (`-newercc` は `-cnewer` と同等）
- m ファイルの更新時間 (`-newermm` は `-newer` と同等）
- t ファイルの代わりに日時を指定

**Unixタイムの取得方法**や**Unixタイムから通常の日時形式に変換する方法**については以下の記事を参照してください。これらを使うことで（移植性がある方法で）自由な日時を生成することが出来ます。

- [シェルスクリプトで日付処理ならdateコマンドは投げ捨ててDateutilsを使おう！](https://qiita.com/ko1nksm/items/46f74707693213a0da3d)
- [date +%s（UNIX時間）に移植性がないのは過去の話](https://qiita.com/ko1nksm/items/93fb75df013af13af877)
- [なぜawkの乱数関数でUnix時間を取得できるのか？ ～ 乱数の話とUnix時間の深い関係](https://qiita.com/ko1nksm/items/9c20739c68d88857ce4c)
- [シェルスクリプトでUNIX時間⇔日付の相互変換を行う関数（POSIX準拠）](https://qiita.com/ko1nksm/items/e61728f863c1b2bc829d)

### -exec や xargs よりもセキュリティに強い -execdir

詳細は省きますが、`-exec` や `xargs` よりもセキュリティに強い `-execdir` が結構多くの環境で実装されています。`-execdir` の方がセキュリティに強いのは事実ですが、最近のコンピュータの使い方は一人一台でログインする人は一人、もしくはサーバー運用で管理者以外ログインしないのがほとんどで、昔のように一台のサーバーに多数のユーザーがログインするようなことはあまりないので、そこまで気にする必要はないと思います。気になる方は以下をどうぞ。

https://www.gnu.org/software/findutils/manual/html_node/find_html/Security-Considerations-for-find.html

（少し古い日本語訳 http://quruli.ivory.ne.jp/document/find_4.2.23/find-ja_8.html ）

### ファイル・ディレクトリを削除するには -delete が便利

すべての環境で実装されているわけではありませんが、ファイルやディレクトリを削除するには `-delete` を使用するのが簡単です。[`-delete` は元は FreeBSD の拡張機能](https://cgit.freebsd.org/src/commit/?id=abacbbbf010ba60f78c755595dfea93a5dfc676c)で、GNU 版にも実装されました。Solaris や AIX は対応していません。

`-delete` を使わずにファイルとディレクトリの両方を削除する場合、`rm` コマンドに `-d` オプションを指定する必要があります。`-d` オプションは `rm` コマンドで指定したファイルに加えてディレクトリ「も」削除するためのオプションです。`-r` オプションとは異なり再帰的に削除するオプションではないので安全です。`rm -d` は POSIX Issue 8 で標準化されるのですが、Solaris や AIX ではまだサポートされていません。サポートされていない場合は `rm -r` で代用するしかありませんが、（将来的には）`rm -d` を使ったほうがよいでしょう。

**追記** [`rm -d` に関する POSIX の標準化の話はこちら](https://www.austingroupbugs.net/view.php?id=802)を参照してください。`rm -d` がサポートされると以下のようなコードで GNU rm の `--one-file-system` 相当のことができるようになるという話をきっかけに標準化される流れを見ることが出来ます。

```sh
find . -xdev -depth ! -name . -exec rm -d {} +
```

**ディレクトリを削除するときは、先にディレクトリ内のファイルを削除する必要があります**。そのために使うのが `-depth` です。`rm` コマンドで削除する場合は `-depth` を付けないとエラーが発生してしまいます。`-delete` を指定すると自動的に `-depth` が指定されるため便利です。

### サブディレクトリ以下を検索しない方法

サブディレクトリ以下を検索しない場合、`-maxdepth` を使用するのが簡単です。GNU、macOS、BSD 系 OS ですでに対応しています。ただし Solaris や AIX では対応していないので移植性を重視するのであれば `-prune` を使って代用します。`-prune` は（それがディレクトリの場合に）ディレクトリ以下の探索を行わない要素式です。

```console:カレントディレクトリのみから検索（ディレクトリを除かなくて良いなら ! -name は不要）
$ find . -maxdepth 1 ! -name . 残りの評価式 # GNU・BSD 系 OS 版
$ find . ! -name . -prune 残りの評価式      # 全対応
```

```console:指定したディレクトリのみから検索（ディレクトリを除かなくて良いなら ! -path は不要）
$ find /tmp -maxdepth 1 ! -path /tmp 残りの評価式 # GNU・BSD 系 OS 版
$ find /tmp ! -path /tmp -prune 残りの評価式      # 全対応
```

## さいごに

`find` コマンドの評価式をオプションと説明しているのを見かけますが、オプションだと認識していると `find` コマンドの動きを理解することは出来ません。評価式は式であると理解すると（特にプログラミング言語の知識を持っている人には）簡単に理解できるでしょう。このような説明をちゃんしているところって案外ないですよね？ちゃんと公式ドキュメントを読めという話なのですが、あれはあれで全ての使い方を書いているものなので、それを読んで理解するのは大変です。