はじめに
find
コマンドの使い方は、ざっくり調べただけではよくわからんとなりますが、見逃しがちなルールを知れば簡単に理解できます。find
コマンドに限りませんが使い方を調べるのが面倒だからと曖昧な理解で使うと逆にもっと分からなくなって時間がかかります。急がば回れ、理解して正しく使ったほうがシンプルで楽で簡単です。この記事では find
コマンドの使い方を理解するために必要なルールと使い方の実践的な知識をまとめました。
Q&A(?): -type
や -perm
の説明はしないの? ⇒ それらはドキュメントを読むか検索すればすぐにわかることで難しいポイントではありません。重要なのは基本のルールを理解することです。
関連記事
- POSIX 準拠のシェルスクリプトでは find | xargs よりも find -exec {} + を使うべき!
- 移植性の話はこちら ⇒ findコマンドのオプション・評価式の一覧と移植性のまとめ
- xargs 完全理解マニュアル - xargs は拡張引数 (extended arguments) の略って知っていますか?
興味がある人への「find コマンドの設計の歴史」の紹介
The History of the Design of Unix’s Find Command
Dennis Ritchieが find コマンドはどうしてこうなった?の質問に答えています。
(もっとも結論は「わからん」なわけですが)
ルール
find
コマンドの検索のルールは省略されている部分を補完してやれば、多くのプログラマにとって馴染みが深いものです。すでに今持っている知識を活用することが出来ます。
ルール1 find コマンドは指定したパスから検索する
find
コマンドは引数で指定したパスから検索します。find
コマンドの構文は次のようになっています。
find [コマンドラインオプション] 検索パス... [評価式]
検索パスは複数指定可能です。ファイルを指定することも出来ますがファイルを指定しても検索の意味はあまりないので通常はディレクトリを指定します。このディレクトリの中からファイルやディレクトリをすべて見つけ出すというのが find
コマンドの最も簡単な使い方です。紛らわしいのは GNU 版の find
コマンドでは検索パスを省略できるという点です。検索パスを省略した場合はカレントディレクトリ (.
) を指定したのと同じ意味になります。移植性がない(macOS などで使えない)ので検索パスは省略しないことをおすすめします。検索パスがあると、どこからファイルやディレクトリを検索しているのかが明確となり、コマンドラインオプションと評価式の区別が付きやすくなります。
ちなみに、次のような書き方は find
コマンドを使う意味はありません。この後に評価式を付け加えれば、全く意味がないというわけではないのですが、シェルの機能によってパス名 (*.txt
) を展開しており、つまりこの場合はファイルを検索しているのはシェルです。
# file1.txt file2.txt file3.txt というファイルがある状態で以下を実行すると・・・
find *.txt
# シェルが以下のファイル名に展開してから find コマンドを実行する(find コマンドは検索していない)
find file1.txt file2.txt file3.txt
# find コマンドの機能で検索する場合の書き方
find . -name '*.txt' # クォートはほぼ必須( *.txt のようにクォートなしで書くのは間違い)
ルール2 検索パスの後ろはコマンドラインオプションではなく評価式
find
コマンドの検索パスの後ろには評価式を書きます。評価式はコマンドラインオプションではありません。コマンドラインオプションは検索パスの前に書くものです。
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 で指定するテーブルに相当します。
find [コマンドラインオプション] 検索パス... [評価式]
よく -name '*.txt'
でテキストファイルを「検索する」とか -type f
でファイルのみを「検索する」とか言いますが、検索パスで指定したパスから見つけ出したファイルまたはディレクトリを-name '*.txt'
や -type f
という条件で絞り込むと言ったほうが適切です。
これを(検索パスを省略した状態で)-name '*.txt'
と -type f
に当てはまるものを検索すると読んでしまうと、カッコや -o
が登場してさらに複雑になった時に混乱します。
ルール4 演算子を省略した時はAND結合
演算子を省略した場合、要素式の間には AND (-a
) が指定されているものとして扱われます。例えば以下の二つは同じ意味です。
-name '*.txt' -type f
-name '*.txt' -a -type f
# 名前が '*.txt' AND タイプがファイル
-exec
を使う場合でも同じです。
-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 で標準化されていません。
また多くのプログラミング言語の式と同じように演算子はショートカット演算子になっています。ショートカット演算子とは評価が確定した段階で残りの評価を打ち切ることです。(結果がわかっているので評価する意味がない)
A and B ・・・ A が偽なら最終的な結果が偽であることは確定しているので B は実行しない
A or B ・・・ A が真なら最終的な結果が真であることは確定しているので B は実行しない
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 でつなげのと同じ意味になります。
-name '*.txt' -type f
'(' -name '*.txt' -a -type f ')' -a -print
昔は(他のアクションを書いていないのに)-print
を書かないと特定の条件の場合に出力されないバグがあったらしく省略しない方が良いと言われていたようですが、現在は -print
を書く必要はないはずです。
実践的な知識
find
コマンドの基本を理解したら、次は実践的な知識です。
-exec は {} にファイル名を差し込んで呼び出しているだけ
「-exec コマンド {} \;
」や「-exec コマンド {} +
」の意味がよくわからないと言っている人が多いようですが、単に {}
の部分に見つけたファイル名を差し込んでコマンドを呼び出しているだけです。
find . -name '*.txt' -exec touch {} \;
# \; は見つけた複数のファイルを一つづつ差し込む
# touch <見つけたファイル名1>
# touch <見つけたファイル名2>
# touch <見つけたファイル名3>
# ︙
find . -name '*.txt' -exec touch {} +
# + は見つけた複数のファイルをまとめて差し込む
# touch <見つけたファイル名1> <見つけたファイル名2> <見つけたファイル名3> ...
後ろの \;
や +
は -exec
の終了のマークです。-exec
も要素式のひとつなのでその後に別の要素式をつなげることが出来ます。その際にどこまでをコマンドに渡す引数なのか、つまり区切りはどこのなのか?を示すマークが必要です。-exec
から \;
または +
の中がコマンド部分で {}
にファイル名を差し込んでコマンドを実行しているだけです。
# 区切り記号がないとどこまでが -exec で実行するコマンドにわたす引数なのか区別できない
find . -name '*.txt' -exec test -s {} \; -print
find . -name '*.txt' -exec [ -s {} ] \; -print # 上記の別の書き方
# 以下と同等
# find . -name '*.txt' ! -empty -print
「-exec ... {} \;」は「-exec ... {} ';'」と同じ意味
シェル言語の文法を正しく理解している人ならすぐにわかることですが、最初は \;
ってなんだ?って思うのではないでしょうか。これは ;
という文字を引数として渡したいけど、;
がシェルのメタ文字なので ';'
と書く・・・代わりに \
でエスケープして文字列にしているだけです。
find . -name '*.txt' -exec touch {} \;
find . -name '*.txt' -exec touch {} ';'
(
と )
も同様にシェルのメタ文字なので、文字列として渡すには '('
や ')'
と書かないといけないのですが \(
や \)
でも構いません。個人的にはシェル特有のエスケープを使った書き方よりも、多くの言語で一般的なクォートを使うほうがわかりやすいと思うのですが、よく見かける例ではエスケープが使われています。
-name などの引数はクォートが必要
-name
などの引数もシェルのメタ文字(*
など)が含まれる場合はクォートが必要です。そうしないと先にシェルが(ファイルが見つかれば)ファイル名に展開してしまいます。パターンにマッチするかどうかはシェルではなく find
コマンドが判断しています。
find . -name *.txt # だめな書き方
find . -name '*.txt' # クォートが必要
なお検索パスの場合は(シェルに展開させたい場合は)クォートは必要ありません。検索パスを複数指定したいことはあまりなさそうではありますが。
find /tmp/dir* -name '*.txt'
# /tmp/dir1 /tmp/dir2 のようなパスが存在していれば
# シェルのパス名展開の機能によってそれらのパスに展開される
find コマンド出力のファイルリストを grep コマンドで絞り込まない
ファイル名の絞り込みには -name
や -path
を使います。これらは POSIX で標準化されています。正規表現ではなく GLOB パターンしか使えませんが通常のユースケースでは十分のはずです。(普通はありませんが)ファイル名に改行が含まれていても正しく動きますし、正規表現に関する罠もなく短くてわかりやすいですね。テキスト処理のための grep
コマンドを違う用途に使い回すのではなく、ファイル検索はファイル検索の専門家に任せましょうということです。どうしても正規表現での絞り込みを使いたい場合は POSIX で標準化されていませんが -regex
も使えます。-regex
は GNU だけではなく macOS や FreeBSD でも対応済みです。
find . -name '*.sh' # こちらを使う
find . | grep '\.sh$' # grepで絞り込まない
find . | grep '.sh' # 間違った正規表現(fish.jpg というファイル名にマッチしてしまう)
対話シェルで雑にやる場合は grep
コマンドを使って絞り込んでも構いません。間違って書いた時に出力を目視で確認して何かがおかしいことに気付けるのであれば。
xargsから-execへの置き換えは簡単で短い
xargs
を書く代わりに -exec
を書くだけです。
# ❌ファイル名に空白やシングル・ダブルクォーテーションが含まれると正常に動かない
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
と組み合わせた場合の問題の一つが、ファイルが見つからない場合でもコマンドが実行されてしまうことです。
$ 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 の方が良いと思いますがインストールが必要です)。並列処理を使うときはコマンドの実行回数が増えてしまう(でないと並列処理できない)ので、こちらも正しく使う必要がありますが、その話はこの記事の対象ではないので割愛します。xargs
はデータの流れが直列でコマンド実行も直列なので -P
オプションを指定しないかぎり並列処理の効果はありません。
「-exec ... {} +」は「-exec ... {} ;」より圧倒的に速くてオススメ
find
で -exec
を使うよりも xargs
の方が速いという主張を見かけますが、それは -exec {} \;
との比較の場合です。-exec {} +
は速く xargs
とほとんど同じです。
なぜ -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 {} +
も同じように呼び出します。
追記 対象ファイルが多い場合は xargs
と同じようにシステムの ARG_MAX
を超えないように引数サイズを制限して呼び出すことは POSIX find で規定された動作です。
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 ... {} +
が標準化されるに至るまでの経緯
注意「-exec ... {} +
」はPOSIXが考え出した仕様ではなくSVR4等で実装済みの仕様を標準化したものです。
xargs
も使い方を正しく知らなければ遅くなります。例えば -I
オプションや -n 1
などを使うとファイルが多い時に劇的に遅くなります。これはファイルごとにコマンドを実行するためです。シェルスクリプトが遅くなる原因の一つが外部コマンドの実行回数です。外部コマンドの実行回数を意識すればシェルスクリプトは問題なるほど遅くはありません。
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
コマンドは以下のような構文になっているため、複数のファイル名を渡せません。
mv [OPTIONS] ファイル名... 移動先ディレクトリ
しかし GNU 版では以下のような構文もサポートしており -exec ... {} +
の使い方に適合しています。これが引数の順番を入れ替えただけの構文が用意されている理由です。
mv [OPTIONS] -t 移動先ディレクトリ ファイル名...
上記の mv
コマンドの使い方は複数のファイルを一つのディレクトリにまとめる使い方でしたが、拡張子を一括変換したい時に mv
コマンドはうまく適合しません。そのような場合は代わりに rename
コマンドを使うと良いでしょう。
# 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
コマンド組み合わせると良いでしょう。
# -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 に準拠していません→理由)。GNU、macOS、Solarisでは -mtime N
の意味は N 日前の「一日の範囲」で、N 日前からその前日の範囲を意味しています。-mtime +N
と -mtime -N
はその「一日の範囲」よりも過去または未来を意味するので -mtime +3
は 4 日前よりも過去、-mtime -3
は 3 日前よりも未来という意味になります。ちなみに1日とは86400分のことで、その日の 0 時を基準にするわけではありません(GNU find には 0 時を基準にする -daystart
があります)。
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 日前よりも未来という意味になります。
3日前 2日前 現在
--------|--------|--------|--------|--------|--------|--------|--------
<=======>
-mtime 3 の範囲
=========================><============================================
-mtime +3 の範囲 -mtime -3 の範囲
実害はほとんどないと思いますが厳密には GNU と macOS・Solaris も細かい動作の違いがあります。詳細は「【find -mtimeに移植性はない】Unix標準コマンドを使ってる限り、どこでも動くシェルスクリプトなんて書けるわけねーだろ」を参照してください。
余談ですが分単位で指定する -mmin
(と類似する)要素式はすでに移植性があります(GNU、macOS、BSD 系 OS、Solaris、AIX で実装済み)。POSIX で標準化されていないからといって使えないとする理由はありません。
厳密に日時を指定したい場合は -newer や -newerXY を使用する
-newer
や -newerXY
を使用するとファイルや指定した日時と比較することが出来ます。-newer
は指定したファイルの更新日時としか比較できませんが POSIX で標準化されています。
任意の日時のファイルは touch
コマンドで作成することが出来ます。
$ 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]
# 書式が分かりづらいので避けるのをオススメ
# 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を使おう!
- date +%s(UNIX時間)に移植性がないのは過去の話
- なぜawkの乱数関数でUnix時間を取得できるのか? ~ 乱数の話とUnix時間の深い関係
- シェルスクリプトでUNIX時間⇔日付の相互変換を行う関数(POSIX準拠)
-exec や xargs よりもセキュリティに強い -execdir
詳細は省きますが、-exec
や xargs
よりもセキュリティに強い -execdir
が結構多くの環境で実装されています。-execdir
の方がセキュリティに強いのは事実ですが、最近のコンピュータの使い方は一人一台でログインする人は一人、もしくはサーバー運用で管理者以外ログインしないのがほとんどで、昔のように一台のサーバーに多数のユーザーがログインするようなことはあまりないので、そこまで気にする必要はないと思います。気になる方は以下をどうぞ。
(少し古い日本語訳 http://quruli.ivory.ne.jp/document/find_4.2.23/find-ja_8.html )
ファイル・ディレクトリを削除するには -delete が便利
すべての環境で実装されているわけではありませんが、ファイルやディレクトリを削除するには -delete
を使用するのが簡単です。-delete
は元は FreeBSD の拡張機能で、GNU 版にも実装されました。Solaris や AIX は対応していません。
-delete
を使わずにファイルとディレクトリの両方を削除する場合、rm
コマンドに -d
オプションを指定する必要があります。-d
オプションは rm
コマンドで指定したファイルに加えてディレクトリ「も」削除するためのオプションです。-r
オプションとは異なり再帰的に削除するオプションではないので安全です。rm -d
は POSIX Issue 8 で標準化されるのですが、Solaris や AIX ではまだサポートされていません。サポートされていない場合は rm -r
で代用するしかありませんが、(将来的には)rm -d
を使ったほうがよいでしょう。
追記 rm -d
に関する POSIX の標準化の話はこちらを参照してください。rm -d
がサポートされると以下のようなコードで GNU rm の --one-file-system
相当のことができるようになるという話をきっかけに標準化される流れを見ることが出来ます。
find . -xdev -depth ! -name . -exec rm -d {} +
ディレクトリを削除するときは、先にディレクトリ内のファイルを削除する必要があります。そのために使うのが -depth
です。rm
コマンドで削除する場合は -depth
を付けないとエラーが発生してしまいます。-delete
を指定すると自動的に -depth
が指定されるため便利です。
サブディレクトリ以下を検索しない方法
サブディレクトリ以下を検索しない場合、-maxdepth
を使用するのが簡単です。GNU、macOS、BSD 系 OS ですでに対応しています。ただし Solaris や AIX では対応していないので移植性を重視するのであれば -prune
を使って代用します。-prune
は(それがディレクトリの場合に)ディレクトリ以下の探索を行わない要素式です。
$ find . -maxdepth 1 ! -name . 残りの評価式 # GNU・BSD 系 OS 版
$ find . ! -name . -prune 残りの評価式 # 全対応
$ find /tmp -maxdepth 1 ! -path /tmp 残りの評価式 # GNU・BSD 系 OS 版
$ find /tmp ! -path /tmp -prune 残りの評価式 # 全対応
さいごに
find
コマンドの評価式をオプションと説明しているのを見かけますが、オプションだと認識していると find
コマンドの動きを理解することは出来ません。評価式は式であると理解すると(特にプログラミング言語の知識を持っている人には)簡単に理解できるでしょう。このような説明をちゃんしているところって案外ないですよね?ちゃんと公式ドキュメントを読めという話なのですが、あれはあれで全ての使い方を書いているものなので、それを読んで理解するのは大変です。