BashとZshの "**" (globstar) の挙動の違い
“**” (globstar) とは
globの構文として使える、0個以上のディレクトリに再帰的にマッチするワイルドカードです。
例えば、a/**/zは a/z, a/b/z, a/b/c/z… にマッチします。
元々はzshで実装され、Zsh 2.2で**の形式として定着したようです1。
その後、Bash 4.0でもglobstarオプションを有効にすることで使えるようになりました2。
globの構文はPOSIX.2にも定義されていますが、そこには**は含まれていないため、処理系によって動作がまちまちになっています。
この記事では、bashとzshでの挙動の違いを紹介します。
BashとZshでの挙動の違い
次のようにディレクトリとファイルを用意し、bash、zshそれぞれでマッチするかどうかを確かめ、結果を表にまとめました。 環境はdocker ubuntu:focal、bash 5.0.16、zsh 5.8です。
.
|-- fileA
`-- subA/
|-- fileB
`-- subB/
|-- fileC
`-- subC/
| pattern | fileA | subA | subA/fileB | subA/subB | subA/subB/fileC | subA/subB/subC |
|---|---|---|---|---|---|---|
| * | bash ◯ zsh ◯ |
bash ◯ zsh ◯ |
bash × zsh × |
bash × zsh × |
bash × zsh × |
bash × zsh × |
| */ | bash × zsh × |
bash ◯ zsh ◯ |
bash × zsh × |
bash × zsh × |
bash × zsh × |
bash × zsh × |
| */* | bash × zsh × |
bash × zsh × |
bash ◯ zsh ◯ |
bash ◯ zsh ◯ |
bash × zsh × |
bash × zsh × |
| */*/ | bash × zsh × |
bash × zsh × |
bash × zsh × |
bash ◯ zsh ◯ |
bash × zsh × |
bash × zsh × |
| ** | bash ◯ zsh ◯ |
bash ◯ zsh ◯ |
bash ◯ zsh × |
bash ◯ zsh × |
bash ◯ zsh × |
bash ◯ zsh × |
| **/ | bash × zsh × |
bash ◯ zsh ◯ |
bash × zsh × |
bash ◯ zsh ◯ |
bash × zsh × |
bash ◯ zsh ◯ |
| **/* | bash ◯ zsh ◯ |
bash ◯ zsh ◯ |
bash ◯ zsh ◯ |
bash ◯ zsh ◯ |
bash ◯ zsh ◯ |
bash ◯ zsh ◯ |
| **/*/ | bash × zsh × |
bash ◯ zsh ◯ |
bash × zsh × |
bash ◯ zsh ◯ |
bash × zsh × |
bash ◯ zsh ◯ |
| */** | bash × zsh × |
bash ◯ zsh × |
bash ◯ zsh ◯ |
bash ◯ zsh ◯ |
bash ◯ zsh × |
bash ◯ zsh × |
| */**/ | bash × zsh × |
bash ◯ zsh ◯ |
bash × zsh × |
bash ◯ zsh ◯ |
bash × zsh × |
bash ◯ zsh ◯ |
| s** | bash × zsh × |
bash ◯ zsh ◯ |
bash × zsh × |
bash × zsh × |
bash × zsh × |
bash × zsh × |
| s**/ | bash × zsh × |
bash ◯ zsh ◯ |
bash × zsh × |
bash × zsh × |
bash × zsh × |
bash × zsh × |
zshでは**/の形のときのみ再帰的にディレクトリとマッチしているのに対し、bashでは**でも再帰的に、ディレクトリだけでなくファイルにもマッチしています。
また、どちらの場合もパターン末尾が/のときはディレクトリにのみマッチします。
もし特定ディレクトリ以下のすべてのファイル・ディレクトリにマッチさせたい場合、bashでは**でもマッチしますが、zshでは**/*としなければなりません。
その他の処理系について
いくつかの言語では標準パッケージとして**をサポートしたglobを提供しています。
手元で調べたところ、pythonはbashと、rubyはzshと同様の動きをしますが、Haskellはまたちょっと違う動作をしていました。
**の挙動は明確に規格化されているわけではないため、混乱した状況は続きそうです。