Gauche:glob
0.8.12までのsys-globはPOSIX.2のglob()関数を使っていたが、windows portでも同じ動作を保証したいのと、'**' とか '{a,b,c}' などの拡張機能も盛りこみたいので、Schemeで実装することにした。
で、globのパターンをregexpに変えてツリーに再帰的にマッチかけてけばいいとたかをくくっていたら、 案外面倒だったのでメモしておく。
面倒なのはパスコンポーネントの先頭に現れる '.' へのマッチだ。これは「パターンのパスコンポーネントの最初に現れる'.'」にしかマッチしない。つまり、 パターン '*' は空の文字列にもマッチ可能なんだけど、パターン '*.' は 名前 "." にはマッチしない。パターン中に現れる '.' がコンポーネントの先頭にないから。
なので普通は '*' -> #/.*/ '?' -> #/./ '.' -> #/\./ という置換でいいんだけど、パスコンポーネントの先頭に現れる次のパターンに 関しては特別扱いする必要がある。先頭の "*" が常に #/[^.].*/ となるわけではない。 パターン "*a" はパスコンポーネント "a" にマッチする必要がある。 先頭の "*" の直後に #[*?.] のいずれかが来た場合のみ #/[^.].*/ が確定する。
* #/([^.].*)?/ ? #/[^.]/ ** #/[^.].*/ *? #/[^.].*/ *. #/[^.].*\./
あと、微妙な違いとして文字セットの反転に[^...]ではなく[!...]を使うというのもある。 ([^...] はPOSIXのglobでは「未定義」となっているので、反転扱いにしてもいい)
'**' の扱い
RubyのDir.globだと、'**' は最後のパスコンポーネントに出現した場合は単なるワイルドカード マッチになるようだ ('*' と実質同じ)。
{} の扱い
zshでもtcshでも、{a,b,c}などは *? などの展開に先立って行われるようだ。 つまり
*/{foo,bar}.c
は
*/foo.c */bar.c
というふたつのパターンを与えたのに等しい。
この仕様は性能的にはちょっと嫌で、例えば
**/*.{cpp,h}
とかやると
**/*.cpp **/*.h
と展開されて、比較的重い "**" の展開処理を複数回行わないといけない。
展開結果の順序の互換性が必要無いなら、**の展開を共用して末端で *.{cpp,h} の展開した方がいい。オプションで切り替えるようにすべきか?
- enami(2007/11/27 01:00:03 PST): 順序の互換性という観点からいうと、 glob(3)はデフォルトでソートしますよね。
- Shiro(2007/11/29 19:19:45 PST): そうかぁ。glob(3)との順序の互換性ってどのくらい 重要でしょうかね。Schemeレベルではソートが欲しければ簡単にできちゃうので、 デフォルトは順序保証無し(のかわりに速いことがある)って方が 使いでがありそうな感じがします。
- enami(2007/12/03 17:09:48 PST): 機械的に変換するんでもなければ、 glob(3)の動作をすべてschemeのglobに求める必要はないでしょうね。 逆に、たとえばbraceの展開とソートという点からは、 展開後の個々のパターンのglob結果をそれぞれソート、という動作なので、 ユーザに同じ動作を見せたいとおもったら、braceの展開だけをする、 という関数もほしくなるかも。
{}の展開の優先順位
上で述べたようにzshでもtcshでも{a,b,c}はまず字面の上で文字列 a b c に 展開され、各々の文字列についてワイルドカードが含まれていればglob展開が 行われる。では、{}の中にカンマを含むワイルドカード文字列を入れたい場合は どうすればいいのだろう。
zshでは不可能っぽい?
[shiro@scherzo]~% touch ab,cd [shiro@scherzo]~% ls ab* ab,cd [shiro@scherzo]~% echo {ab,cd*} zsh: no matches found: cd* # 'ab' と 'cd*' に展開 [shiro@scherzo]~% echo {ab\,cd*} zsh: no matches found: {ab,cd*} # 展開されず{ab,cd*}になった [shiro@scherzo]~% echo {ab\\,cd*} zsh: no matches found: cd* # 'ab\' と 'cd*' に展開されたかな [shiro@scherzo]~% echo {ab[,]cd*} zsh: bad pattern: ab[ # 'ab[' と ']cd' に展開 [shiro@scherzo]~% echo {ab[\,]cd*} zsh: no matches found: {ab[,]cd*} # 展開されず{ab[,]cd*}になった
いや、違うな。'{}'の中にエスケープされていないカンマがあった時のみ ブレースが展開されるのか。
[shiro@scherzo]~% echo {ab\,cd*,z} ab,cd z # 'ab,cd*' と 'z' に展開。前者のみglob [shiro@scherzo]~% echo {ab[\,]cd*,z} ab,cd z # 'ab[,]cd*' と 'z' に展開。前者のみglob
シェルの場合、ファイルのglobだけでなく文字列展開をやりたい場合もあるわけで、 {}が両者を兼ねていると考えることもできる。 しかしScheme関数としてのglobはファイルのglobをやるに決まってるんだから、 ちょっとこの仕様はふさわしくないかもしれない。