ちょっとだけカッコよく find(1) を使う
UNIX には「find」というコマンドがあります。 名前を見ればわかるように、特定のファイルを検索するためのコマンドなのですが、 意外と使うのが難しいことで定評があります。そのため、多くの人は単に「名前で検索するため」だけに find コマンドを使っているのではないでしょうか。 そんな勿体無い事態から脱却して、ちょっとだけ「カッコよく」find を使うためのネタを纏めてみました。
find(1) の概要
「ファイルを検索する」という機能は、 MacOS にも Windows にも備わっています。 無論 UNIX にも備わっているわけで、それには「find」というコマンドを使います。
find を使うと、指定したディレクトリの下にある全てのファイルのうち、 指定した条件に合致したものを検索することが出来ます。 勿論、指定したディレクトリの下に更にディレクトリがある場合は、その中にあるファイルも検索の対象となります。
ところで、man(1) で find(1) について最初に調べたとき、 多くの人は「よく分からん」で終わってしまうのではないでしょうか。 find(1) の書式というのは、一見「どう使えばいいのか」と疑問を抱かせるようなものなのです。
find [path...] [expression]
ちゃんと man を読めばわかるのですが、最初の「path」は「検索を開始するディレクトリ」、
次の「expression」は「評価式」となっています。これらは夫々、複数指定することが可能です。
単純な例を挙げてみましょう。これは「/ ディレクトリ以下の全ファイルのうち、名前が
index.html であるものを表示」するものです。
$ find / -name index.html -print
最初の「/」が path に相当する部分です。
ここでは、「検索を開始するディレクトリは / である」ということを指示しています。
それ以降は全て expression となります。まず、「-name index.html」
は「名前が index.html である」ことを意味します。
最後の「-print」は「検索された結果を表示する」を意味します。
find(1) が一見ややこしく見えるのは、
「expression の部分の指定方法がややこしいから」だと思います。
expressionの部分にはオプション、検索条件、アクションが指定出来るのですが、まずはこの
3 つの違いについて、簡単に纏めてみましょう。
- オプション
- find(1) の全体的な処理を指定する。
- 検索条件
- 検索するファイルの条件を指定する。
- アクション
- 検索されたファイルに対する動作を指定する。
find(1) の動作を極めて大雑把に定義してしまうと、 「検索条件に合致するファイルに対して、指定されたアクションを実行する」となります。 ですから、検索条件とアクションの指定方法さえ掴んでしまえば、「カッコよく」使えるようになるというわけなのです。
検索条件のいろいろ
find(1) で指定出来る検索条件には実に様々なものがあるのですが、「ちょっとカッコよく使うため」なら、この 4 つを覚えておけば十分です。
名前で検索
「これこれという名前のファイルを探したい」というのは、おそらく一番基本的な要求ではないでしょうか。
このときに使う判別式は先程紹介した「-name ファイル名」です。
ファイル名にはシェルのワイルドカード(. 及び
*)が使用出来ますが、ワイルドカードを使う際は引用符で括るか、バックスラッシュでエスケープする必要があります。
「拡張子が .gif であるファイルを全部表示する」ための例を挙げてみましょう。
ここでは「*」を使っていますので、ファイル名のパターン全体を「"」で括っています。
$ find / -name "*.gif" -print
大きさで検索
ディスク容量が逼迫してきたときなど、「どのファイルがそんなにディスクを消費しているのだろう」などとを考えるのではないでしょうか。 find(1) を使うと、「あるサイズよりも大きいファイル」や「あるサイズよりも小さいファイル」を条件として、ファイルを検索出来ます。
サイズを条件として検索するには「-size サイズ」を使用します。
但し、幾つかの指定事項がありますので気をつけて下さい。
- 「より大きい」「より小さい」を指定するために、サイズの前には 「
+」か 「-」を付けます。+は「指定サイズよりも大きい」ことを、-は「指定サイズよりも小さい」ことを意味します。+も-も付けなかった場合は、「ぴったりそのサイズ」という意味になりますので、気をつけて下さい。 - ファイルサイズの単位を指定するためには、サイズの後ろに「
c」か 「k」を付けます。cはバイト単位、kはキロバイト単位を意味します。cもkも指定しなかった場合は「ブロック単位(512 バイト)」が利用されますが、 ブロックという概念は一般の方には馴染みがないでしょうから、 「サイズ指定の最後には、常にcかkが必要」 と覚えておけばよいでしょう。なお、バイト単位を意味するcは、 おそらく「character(文字)」の「c」だと思われます。
それでは、具体的な例を挙げてみましょう。
- サイズが 100KB より大きいファイルを検索
- サイズが 2869 バイトのファイルを検索
- サイズが 200 バイトより小さいファイルを検索
という場合は、それぞれ次のようになります。
$ find / -size +100k -print $ find / -size 2869c -print $ find / -size -200c -print
日付で検索
find(1) を使うと、「指定した日時より後に作られたファイル」といった条件で検索が出来ます。
アクセス日を対象にするなら、「-atime 日数」、更新日を対象にするなら
「-mtime 日数」、作成日を対象にするなら「-ctime 日数」がそれぞれ使えます。
ところで、find(1) の日付の指定方法は、実はちょっと分かり難いです。
- 「日数」は、「今日から何日前」ということを意味します。
例えば日数として「
7」を指定した場合は、「7 日前」を意味します。 - 「日数」の前に「
-」が指定されると、「今日から何日前より新しいファイル」という意味になります。 「-7」を指定した場合は、「7 日前より新しいファイル」を意味します。 - 「日数」の前に「
+」が指定されると、「今日から何日前より古いファイル」という意味になります。 「+7」を指定した場合は、「7 日前より古いファイル」を意味します。
「一週間以内にアクセスがあったファイル」と、「一週間以上前に作成されたファイル」を検索する例をそれぞれ挙げてみましょう。
$ find / -atime -7 -print $ find / -ctime +7 -print
ファイルタイプで検索
「検索対象はファイルだけで、ディレクトリはどうでもいい」
などという場合は、ファイルタイプを指定することが出来ます。
指定には「-type ファイルタイプ」を使います。
ファイルタイプは 1 文字で指定します。通常使うのはファイルを意味する「f」と、ディレクトリを意味する
「d」だけですので、この 2 つを覚えておけばよいでしょう。
検索条件の組み合わせ
ただ単に上の 4 つを使っているだけでは、ちょっと手の込んだ検索をしようと思ったときに行き詰まってしまいます。
でも検索条件を組み合わせる方法を知っておくと、「一週間以内に更新された index.html というファイル」
などといった条件でファイルを検索出来たりします。
find(1) の動作
さて、条件の組み合わせについて書く前に、今まではしょってきた「find(1) の動作」について説明しないといけません。
find(1) は、path で指定されたディレクトリ以下のファイル一つ一つに対して、expression
部分を適用し、評価します。一つのファイルについて expression
部分の評価が終わったら、次のファイルに対して同じ処理を行います。これを
pathで指定されたディレクトリ以下のファイル全てに対して行うわけです。
ところが分かり難いことに、find(1) は
指定された条件の真偽が確定したら、その瞬間にその後の評価をやめてしまいます。
最初に書いたように、 find(1) の
expression 部分は「オプション」「検索条件」「アクション」で構成されています。
これらは評価されるとそれぞれ「真」か「偽」を返してきます。
これには、次のような規則があります。
- 「オプション」は常に「真」を返します。
- 「検索条件」は、現在調べているファイルが条件に合致すれば「真」、そうでなければ「偽」を返します。
- 「アクション」は、アクションの実行に成功したら「真」、そうでなければ「偽」を返します。
今までは特に何も断らず、「検索条件」と「-print」を並べて書いていましたが、
実は「検索条件」とアクションである「-print」は AND の関係にあります。
「-atime -7 -print」とは、実は「-atime -7and
-print」を意味しています。細かく書くとこうなります。
- find(1) は、「
-atime -7」という条件を判定します。 - 「
-atime -7」の結果が「真」…つまり現在調べているファイルが一週間以内にアクセスされたものなら、 find(1) は「-print」の部分を実行して 「-atime -7 -print」の結果を確定させようとします。 - 「
-atime -7」の結果が「偽」なら、「-atime -7 -print」の結果は 「-print」の実行結果がどうであれ「偽」になりますから、find(1) は「-print」の部分を実行しません。
ちょっと分かり難い部分なのですが、 find(1) で凝ったことをしようとしたときにこの動作原理を把握していないと、 なかなか思い通りの動作を指せることが出来ません。というわけで、ちゃんと押さえておいて下さい。
条件の AND
先程例に出した「一週間以内に更新された index.html
というファイル」、このようなファイルを検索するときは、条件を順に並べれば
OK です。並べられて書かれた条件は「AND」…つまり、「〜であり、〜である」という条件を意味します。
$ find / -name index.html -mtime -7 -print
更に「サイズが 4KB より大きい」といった条件を追加するなら、同じように条件を並べましょう。
「名前が index.html であり、一週間以内に更新されたファイルであり、サイズが
4KB より大きい」という意味になります。
$ find / -name index.html -mtime -7 -size +4k -print
条件の OR
「〜か、または〜」というような場合、条件の間に「-o」を入れましょう。
「名前が index.html か、top.html
であるファイル」を検索するときは、このような感じになります。
$ find / \( -name index.html -o -name top.html \) -print
「-name index.html」という条件と、「-name top.html」という条件が「-o」で結ばれているのがおわかりでしょうか。
この「-o」によって、「名前が index.html」か「名前が
top.html」か、という条件が成立しています。
ところで、上のコマンドラインに出てくる「\(」と「\)」は一体何なのでしょう。これは、
「or の優先順位は and より低い」ことによる制限です。もし、この式を
$ find / -name index.html -o -name top.html -print
と書いた場合、何が起こるかを考えてみましょう。
-
まず、 find(1) はファイルの名称が
index.htmlであるかどうかの判定をします。 -
もしファイルの名称が
index.htmlの場合、この段階で 「『-name index.html』と 『-name top.html -print』の or」は 「真」であることが確定してしまいます。 すると find(1) はこの段階で評価をやめてしまい、発見されたindex.htmlを表示せずに次のファイルの評価を始めます。 -
ファイルの名称が
index.htmlでない場合は、top.htmlかどうかを判定します。 もしファイル名がtop.htmlである場合には、 find(1) は続く-printを実行して「-name top.html -print」の真偽を決定しようとします。 -
ファイルの名称が
top.htmlでもない場合は、 「-name top.html -print」の「偽」が確定しますので、 find(1) はそこで処理をやめ、次のファイルの評価を始めます。
つまり、目的は「ファイル名が index.html か top.html
のときにファイル名を表示したい」ということなのに、ファイル名が top.html
のときしか -print が実行されない、ということになってしまいます。
そこで、「-name index.html -o -name top.html」を先に計算させるために「(」と
「)」で式を括っているのですが、「(」と「)」は先にシェルが解釈してしまうので、
シェルによる解釈を避けるべく「(」と「)」をバックスラッシュでエスケープしている、というわけです。
グルーピングの括弧
先程の「-o」の説明を読んで混乱された人もいるかもしれませんが、
find(1) を使うときには、常に expression
部分の各項目の間の優先順位に気を配る必要があります。具体的には、
- 単純に並べた場合、各項目は and で結合される。
- 「
-o」、つまり or で結ばれた演算は、and で結ばれた演算より優先順位が低い。
ということです。ですから、「-o」を使う場合、必要であれば括弧で式を括って、
優先順位を上げてあげないといけません。
ところが面倒なことに、コマンドラインから find(1) を使う場合、
find(1) に引数が渡される前にシェルがその引数を解釈してしまいます。シェルにとって「(」と
「) 」は意味がある文字ですので、シェルに対して「この括弧は解釈するな」と伝なければなけません。
そのため、 find(1) で「(」と「)」を使う場合は「(」と「)」をエスケープするため、「\(」「\)」と書く必要があります
(「(」と「)」のエスケープの方法として、「"("」「")"」や「'('」「')'」を利用しても大丈夫です)。
更に面倒なことに、「(」と「)」は find(1)
では独立した引数として扱われます。そのため、「\(」と「\)」の前後には必ず空白を入れる必要があります。この点にも気をつけて下さい。
条件の否定
「!」を使って、条件の否定…つまり、not 演算を行うことが出来ます。
但し、やはり「!」はシェルにとって意味がある文字ですので「\!」と書いてエスケープしないといけません。
「\!」を使うと、いろいろと便利なことが出来ます。次の例は、「1 ヵ月間全然アクセスしなかったファイル」を検索するためのものです。
$ find / \! -atime +30 -print
1 ヵ月間も全然アクセスしなかったファイルは、当面不要だと考えてもいいでしょう。 となると、バックアップメディアにしまいこんでおくか、思いきって消してしまう…という手段が取れます。 これはディスクが逼迫しているときなど、非常に有効です。
次の例は、「log.txt」というファイル以外の「*.txt」というファイルを検索するものです。
$ find / \! -name -log.txt -name "*.txt" -print
not の優先順位は and よりも高いので、最初の「-name -log.txt」だけが否定されていることに気をつけて下さいね。
find の真骨頂…アクションを指定する
ただ単にファイルを検索するだけでしたら、find(1) の意味はあまり大きくありません。 find(1) の強みは 「検索されたファイルに対して様々な処理を行える」というところにあります。
-exec を使ってコマンドを実行する
「-exec」を使うと、検索されたファイルに対して任意のコマンドを実行出来ます。
検索されたファイルをその場で消してしまったり、バックアップメディアにコピーしたり、といったことも可能になります。
尤も、-exec の書式は、例のごとく分かり難いものなのですが。
-exec コマンド名 {} \;
-execに続けて、任意のコマンドを指定します。
続く「{}」は、 find(1) で検索されたファイル名で置き換えられます
(*1) 。
最後の「\;」はコマンド列の終了を示すためのものです。
本来は「;」なのですが、シェルにとって「; 」は意味のある文字のため、
「\;」としてエスケープしなければなりません。
また、「;」は find(1) にとって独立した引数なので、
「(」や「)」と同様、やはり前後に空白を入れる必要があります(*2)。
説明はこのくらいにして、例を示しましょう。
先程挙げた例をちょっと変えて、検索されたファイルを即座に消すようにしています。
今まではアクションとして -print
しか使ってこなかったので、ただ単に検索されたファイルを表示するだけでした。
しかし、この例では検索されたファイルに対して rm(1) を実行するようにしています。
$ find / \! -name -log.txt -name "*.txt" -exec rm {} \;
次の例は、カレントディレクトリ以下のファイルのうち、
1 ヵ月間全然アクセスされなかったファイルのみ(ディレクトリは含まない)
を /home/backup というディレクトリに移動するものです。
$ find . \! -atime +30 -type f -exec mv {} /home/backup \;
次の例は、/home/data/doc 以下のファイルのうち、サイズが
100KB より大きい「*.txt」ファイルについて、
文字数、単語数、行数を調べるものです。文字数、単語数、行数を調べるために wc(1) を使っています。
$ find /home/data/doc -size +100k -type f -exec wc {} \;-ok の使い方
先ほど
-execの使い方を説明しましたけど、ただ単純にコマンドを適用するならともかく、
「ホントに一様にコマンドを実行してもいいのか」と悩む局面は意外と多いものです。
特に、ファイルを消去する rm(1) を使うようなときはそうでしょう。
そのため、 find(1) はユーザにコマンドの実行を確認するための「-ok」
と呼ばれるアクションを備えています。使用方法は -exec と同じです。
$ find / \! -name -log.txt -name "*.txt" -ok rm {} \;条件に適合するファイルを発見すると、find(1) は次のようなプロンプトを出してきます(以下の例は GNU findutils version 4.1 に含まれる find が出力したものです)。
< rm ... ./dora.txt > ?
「?」プロンプトに続けて「y」をタイプすると、
「-ok」に続くコマンドを実行します。
また、「y」から始まらない文字をタイプすると、何もせずに次のファイルを検索しに行きます。
最後に
とりあえず、「ちょっとだけカッコよく」find(1) を使うためにはこの程度の知識があれば十分だと思います。 あとは、man find をタイプして、そのほかの機能を調べてみて下さい。
どうしても find(1) の構文を覚えるのが面倒、という人は、find(1) の GUI フロントエンドを使うという手があります。 ただ、もし貴方が将来 UNIX マシンのお守りを命じられたとき、 find(1) を知らないと辛い思いをするのは間違いありません。 そのときのために、find(1) の使い方くらいはは覚えておくべきでしょう。