Gauche:glob

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} の展開した方がいい。オプションで切り替えるようにすべきか?

{}の展開の優先順位

上で述べたように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をやるに決まってるんだから、 ちょっとこの仕様はふさわしくないかもしれない。

More ...