2012/04/25

[余談] ファイルを探索後、そのファイルを検索する

ちと本日ネタになったので覚え書き代わりに書くとする。

本日みたある資料では、以下のような感じになっていた。
grep ERROR `find . -name "debug*.txt" -print`
これがいけないこと、というのを指摘したのだが分かってもらえなかったようなので何故いけないかをつらつら語りたいと思う。


ある程度 UNIXの経験がある人ならば「何だその話かよ」で終わる話だが、様々な面でこれは問題のある表現である。


一番簡単な問題は「溢れる」ということだ。コマンド行の固定長で上限ある。
このサイズは ARG_MAX という定数で定義されている。

例えば、Mac OS X 10.5.8 では以下の感じだ。
grep -R ARG_MAX /usr/include/
(中略)
/usr/include/limits.h:#define _POSIX_ARG_MAX 4096
/usr/include/sys/syslimits.h:#define ARG_MAX   (256 * 1024) /* max bytes for an exec function */
POSIX な定数だとわずか 4KB, そうでなくても 256KB 程度になる。これ以上は保証されない。

上記の find の書き方だと「カレントディレクトリ (.) とそこから下のサブディレクトリ」を再帰するので、debug.txt と書かれたファイルが下のディレクトリにあるなら容易に溢れる訳だ。溢れると、のたうち回った末にこんなエラーが表示される

/usr/bin/grep: Argument list too long.
計算機資源に優しくないのでやめろ感じる訳だ。

また、サブディレクトリを探す意図はなかったというかも知れないが、ならばシェルの補完でも十分だ。「 grep ERROR debug*.txt 」で間に合う。どうしてもバッククオートを使いたいなら「grep ERROR `ls debug*.txt`」とでもすればいい。


もちろん「溢れ」の可能性については前世紀から分かってる事実であり、このため find には頼もしい助っ人である「xargs」コマンドがついている。

上記の例を「悪い」見本で直すとこんな感じだ
find . -name "debug*.txt" -print | xargs grep ERROR
xargs は標準入力で渡されたリストを ARG_MAX を越えない程度に分割して、それを引数で渡されたコマンドの引数にして実行する。また、コマンドの実行が一々増えていいのなら

find . -name "debug*.txt" -exec grep ERROR {}\;
と書けばいい。-exec は find がマッチしたファイルパスの一つ一つに対して指定のコマンドを実行する。{} の部分がファイルパスに置換される。また、-exec の引数となるコマンド列の末尾は ; で終わるが、これがシェルに解釈されないように \ を入れる。-exec hogehoge {} \; まではイディオムであり、よく見かけると思われる。

個人的には、上記二つで普通のUNIX屋であると認識する。


で、より上位の人に「一々 -exec するな」か「マッチしたファイル名に空白がはいってたらどうする」かで突っ込まれるのもまた普通の話と思われる。

そう、xargs で引き渡された標準入力に、普通の英数字以外が混じっているとそこが「区切り」と見なされることがある。


で、だ。そんなことも、xargs を作ったような在りし日のUNIX屋にとっては想定内である。そのため、find には -print0, xargs には -0 オプションがある。正しいのは以下になるだろう。
find . -name "debug*.txt" -print0 | xargs -0 grep ERROR
find の -print0 オプションは -print とほぼ同じだが、区切り文字を '\0'、ヌル文字にしてくれる。空白や制御文字はファイル名に入れることは出来ても、ヌル文字は決してファイル名に現れない。このため、一つの検出されたファイルパスが間違って分割されることもない。

一方、ヌル文字が区切りであることを示すのに、xargs には -0 (ゼロ)オプションがある。find の -print0 での出力を xargs -0 は正しく解釈し、ARG_MAXに越えないだけにまとめてコマンド実行をしてくれる。

いつ頃から find -print0 と xargs -0 があったかは知らないが、私が駆け出しの頃にはすでにあったものだ。今日日の環境ならどこにでもあるだろう。

ファイル名に空白があるは、かつて Apple も iTunes のアップデートでミスった問題だ。
決して少ない訳じゃない。


決して新しい知識ではないのだから、ここまでは配慮して欲しいと思ったのが、本日ぐだぐだ言ってた理由だ。