LoginSignup
4
0

More than 3 years have passed since last update.

大文字小文字を区別しないという不幸な世界のワイルドカード(macOS)

Posted at

この記事について

macOS のファイルシステムは、デフォルトでは殆どのディレクトリで大文字小文字を区別しない。
そのような世界で、ワイルドカードを用いると何がマッチするか、という話。

大文字小文字を区別しないということ

例えばこんなスクリプトを実行すると

bash

#!/bin/bash

set -eu

# ASCII の英数字
touch foo.txt FOO.txt
ls *.txt && rm *.txt

# 所謂全角文字
touch zen.txt ZEN.txt
ls *.txt && rm *.txt

# ギリシャ文字・キリル文字・ローマ数字
touch ωяⅶ.txt ΩЯⅦ.txt
ls *.txt && rm *.txt

# DZ, NJ
touch dz.txt # U+01F3   Latin Small Letter DZ
touch Dz.txt # U+01F2   Latin Capital Letter D with Small Letter z
touch DZ.txt # U+01F1   Latin Capital Letter DZ
touch nj.txt # U+01CC   Latin Small Letter NJ
touch Nj.txt # U+01CB   Latin Capital Letter N with Small Letter J
touch NJ.txt # U+01CA   Latin Capital Letter NJ
ls *.txt && rm *.txt

# i witout dot, etc.
touch ı.txt # U+0131 Small I without dot
touch İ.txt # U+0130 Capital I with dot
touch i.txt # U+0069 Small I
touch I.txt # U+0049 Capital I
ls *.txt && rm *.txt

# 囲み文字
touch ⓐ.txt # U+24D0
touch Ⓐ.txt # U+24B6
ls *.txt && rm *.txt

こんな結果になる

foo.txt
zen.txt
ωяⅶ.txt
nj.txt  dz.txt
i.txt   İ.txt  ı.txt
ⓐ.txt

FOOfoo は簡単。大文字小文字を区別しないのでどちらかしか生き残れない。

zenωяⅶ のあたりを見て分かる通り、7bit-ASCII の範囲外の文字にも大文字小文字があり、それらは区別されない。

Dz は「Titlecase Letter」というカテゴリの文字で、大文字でも小文字でもない。
対応する小文字は dz で、対応する大文字は DZ になる。
この文字も大文字小文字を区別しないので、 touch dz.txt Dz.txt DZ.txt としても、ひとつしか生き残れない。

ı, İ, i, I の件は下表のとおり。

言語 Iの小文字 iの大文字
英語 i I
トルコ語 ı İ

ı.txt, İ.txt, i.txt, I.txt を touch すると、
i.txt, İ.txt, ı.txt が生き残る。

unicode.org を見ると、dot のない小文字ıは、大文字にすると普通のI になる。
にも関わらず、APFS 上では ı.txtI.txt は別の名前だとみなされる。

様々な環境での対応

いくつかの環境で何が起こるのか試してみた。

shell script(bash) 他

以下の環境でこの結果になる:

  • shell script(bash)
  • C(POSIX glob)
  • Go(filepath.Glob)
  • Python3(glob.glob)
  • PHP7(glob)

POSIX glob や bash と同じなのでこれが基本っぽいんだけど、わりと気持ち悪い動作になっている。

基本的には、

  • ワイルドカードがない場合は大文字小文字を区別しない。
  • ワイルドカードがある場合は大文字小文字を区別する。

となっている。Foo.txt だと1件マッチして F*.txt だと 0件マッチするというのはかなり意外だった。

ワイルドカードがない部分については、(気付いた範囲では)ファイルシステムと同じ意見になっている。

F*/f*/Foo

wildcard foo.txt fred.txt
F*.txt
f*.txt
Foo.txt

i / I / ı / İ

wildcard i-lat-lo.txt I-lat-up.txt ı-tur-lo.txt İ-tur-up.txt
i*.txt
I*.txt
ı*.txt
İ*.txt
İ-tur-lo.txt
I-tur-lo.txt
ı-lat-up.txt

DZ / Dz / dz

wildcard DZ-uu.txt Dz-ul.txt dz-ll.txt
DZ*.txt
Dz*.txt
dz*.txt
dz-uu.txt
dz-ul.txt
Dz-uu.txt

ruby(Dir.glob)

ruby の動作は POSIX glob とはだいぶ異なる。

基本的には「大文字と小文字を区別しない」という動作で一貫しているような感じ。
Foo.txt だと foo.txt のみがマッチして、 F*.txt だと foo.txtfred.txt がマッチする。わかりやすい。

しかし、

ruby
Dir.glob("dz-u*.txt") #=> []
Dir.glob("dz-uu.txt") #=> ["files/DZ-uu.txt"]

と、ワイルドカードを入れるとマッチする件数が減るというパターンもある。
バグ?

F*/f*/Foo

wildcard foo.txt fred.txt
F*.txt
f*.txt
Foo.txt

i / I / ı / İ

wildcard i-lat-lo.txt I-lat-up.txt ı-tur-lo.txt İ-tur-up.txt
i*.txt
I*.txt
ı*.txt
İ*.txt
İ-tur-lo.txt
I-tur-lo.txt
ı-lat-up.txt

DZ / Dz / dz

wildcard DZ-uu.txt Dz-ul.txt dz-ll.txt
DZ*.txt
Dz*.txt
dz*.txt
dz-uu.txt
dz-ul.txt
Dz-uu.txt

Java(PathMatcher)

java.nio.filePathMatcher という インターフェイスがあるので、それを使ってみた。
これも POSIX glob とはだいぶ異なる。
常に大文字小文字を区別するように見える。
ファイルシステムのファイル名とは異なる動作だが、一貫性はある。

F*/f*/Foo

wildcard foo.txt fred.txt
F*.txt
f*.txt
Foo.txt

i / I / ı / İ

wildcard i-lat-lo.txt I-lat-up.txt ı-tur-lo.txt İ-tur-up.txt
i*.txt
I*.txt
ı*.txt
İ*.txt
İ-tur-lo.txt
I-tur-lo.txt
ı-lat-up.txt

DZ / Dz / dz

wildcard DZ-uu.txt Dz-ul.txt dz-ll.txt
DZ*.txt
Dz*.txt
dz*.txt
dz-uu.txt
dz-ul.txt
Dz-uu.txt

C#(.NET Core / Directory.GetFiles)

ruby の動きと似ている。
ruby と異なり、 DZ*.txtdz-ll.txt にちゃんと(?)マッチする。
しかし逆に、dz-uu.txt では DZ-uu.txt が取れない。

F*/f*/Foo

wildcard foo.txt fred.txt
F*.txt
f*.txt
Foo.txt

i / I / ı / İ

wildcard i-lat-lo.txt I-lat-up.txt ı-tur-lo.txt İ-tur-up.txt
i*.txt
I*.txt
ı*.txt
İ*.txt
İ-tur-lo.txt
I-tur-lo.txt
ı-lat-up.txt

DZ / Dz / dz

wildcard DZ-uu.txt Dz-ul.txt dz-ll.txt
DZ*.txt
Dz*.txt
dz*.txt
dz-uu.txt
dz-ul.txt
Dz-uu.txt

C#(Mono / Directory.GetFiles)

意外にも、 .NET Core と Mono で動作が異なる。
大文字でも小文字でもない Dz という文字に負けている感じがする。

F*/f*/Foo

wildcard foo.txt fred.txt
F*.txt
f*.txt
Foo.txt

i / I / ı / İ

wildcard i-lat-lo.txt I-lat-up.txt ı-tur-lo.txt İ-tur-up.txt
i*.txt
I*.txt
ı*.txt
İ*.txt
İ-tur-lo.txt
I-tur-lo.txt
ı-lat-up.txt

DZ / Dz / dz

wildcard DZ-uu.txt Dz-ul.txt dz-ll.txt
DZ*.txt
Dz*.txt
dz*.txt
dz-uu.txt
dz-ul.txt
Dz-uu.txt

Perl(glob)

POSIX glob とよく似た動作になっているが、ドットなしの小文字 i の扱いが異なる。

F*/f*/Foo

wildcard foo.txt fred.txt
F*.txt
f*.txt
Foo.txt

i / I / ı / İ

wildcard i-lat-lo.txt I-lat-up.txt ı-tur-lo.txt İ-tur-up.txt
i*.txt
I*.txt
ı*.txt
İ*.txt
İ-tur-lo.txt
I-tur-lo.txt
ı-lat-up.txt

DZ / Dz / dz

wildcard DZ-uu.txt Dz-ul.txt dz-ll.txt
DZ*.txt
Dz*.txt
dz*.txt
dz-uu.txt
dz-ul.txt
Dz-uu.txt

まとめ

POSIX glob は、ワイルドカードがない部分についてはファイルシステムと同じ意見になっているものの、ワイルドカードが入ると大文字小文字を区別するようになるところがわかりにくい。

  • Go(filepath.Glob)
  • Python3(glob.glob)
  • PHP7(glob)

は、POSIX glob と同じ意見になっている。

一方

  • ruby(Dir.glob)
  • Java(PathMatcher)
  • C#(.NET Core GetFiles)
  • C#(Mono Directory.GetFiles)
  • Perl(glob)

は独自のアルゴリズムで処理しているらしく、POSIX glob とは異なる結果を返す。
「アルファベット2文字をセットにした文字で一文字目だけ大文字に出来るもの」や「小文字のiからドットを除いたもの」のあたりで不穏な動作になりやすい。

4
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0