hnwの日記

ポータブルなシェルスクリプトによるパズル (1) find


追記:内容を伴っていなかったのでタイトルを変更しました。どう考えても入門講座になる気配がありません。


以前の記事「シェルスクリプトでシンボリックリンク先が同一かチェックする方法」での疑問点は「自分の書いたシェルスクリプトがポータブルかどうか調べる方法って何だろう?」ということでしたが、id:odzさん、きむら(K)さん、tsekineさんの3人から「POSIXのman page見たら?」とアドバイスを頂きました。ありがとうございます!なるほど、こんな便利なドキュメントがあるんですね。


これさえあれば僕にも模範的なシェルスクリプトが書けちゃうぞ!ということで連載記事化します。第一回目のお題はfindコマンドです。


今回、findに関して同内容の質問を2つ見つけました。GNUじゃないfindで-maxdepth 1相当のことがやりたいそうです。


ではポータブルな方法を考えてみます。早速POSIXのfindコマンドのman pageを見てみると、オプション少なっ!Solarisに付属するman pageも見つけましたが、やはりオプションが少なく感じます。上の質問者の方々も苦労するわけだ…。


そんな-maxdepth 1相当を実現する方法ですが、より良い解答例を考えてみました。

gfind . -maxdepth 1 -type f -name '*.txt'


GNU findの上記コマンドと同じ結果を、由緒正しいfindコマンドでも下記のようにすれば得られます。

find . -type d '!' -name . -prune -o -type f -name '*.txt' -print


ただ、そもそもこれが読み解けない人もいると思いますので、簡単に説明します。

-pruneとは

findの-pruneオプションは、「条件に合うディレクトリ以下およびファイルをチェック対象にしない」という指定です。


一方、-printというのは「条件に合ったディレクトリ名やファイル名を出力する」というオプションで、何も指定しない場合には勝手に補われます。-pruneなどを使わない限り-printを指定することも無いでしょうから、知らずに使っている人も多いのではないでしょうか。


また、-oは「or」の意味です。


上のコマンドを改めて読み解くと、「"."という名前じゃないディレクトリ以下はチェックしない。また、"*.txt"にマッチするファイルが見つかったらファイル名を出力する」ということになります。「.を検索対象にして、.以外のディレクトリをpruneする」というのがミソです。


特定のディレクトリ以下で検索したい場合もパス名の最後に"/."をつければうまく動きます。

find /path/to/search/. -type d '!' -name . -prune -o -type f -name '*.txt' -print


このやり方はパズル的な意味で面白いと思うんですが、仕事で書く場合は避けた方がいいかもしれません。補足しておくとOpenSolaris標準のfindでも期待通りに動きましたし、私の手近の環境では全て動いているようですので、使えないことは無いと思うんですが、トリッキーすぎる気がします。


-maxdepth 2相当も-execから更にfindを呼び出せば実現できますが、そこまでやると目的を見失ってるとしか言いようが無いですよね。やはりPOSIXなfindでやるには複雑すぎるということでしょう。GNU findって素晴らしいものですね。実はFreeBSDMacOSXのfindもかなり高機能で、-maxdepthが使えます。

まとめ

ポータブルなシェルスクリプトを書こうと思ったら制約が多いんですね。-notも方言とは予想外でした。複雑なことがしたくなったらPerlとかPythonとかで書いた方がいいかもしれません。いきなりひどい結論ですけど。


どうしてもポータブルなシェルスクリプトを書く必要がある場合はOpenSolarisを入手しておくと良いと思います。Solarisの標準コマンドは意外と融通のきかないものが多いので、テスト環境として大活躍するのではないでしょうか。