web-dev-qa-db-ja.com

[A-Z]がbashの小文字に一致するのはなぜですか?

私が知っているすべてのシェルで、rm [A-Z]*は、大文字で始まるすべてのファイルを削除しますが、bashを使用すると、文字で始まるすべてのファイルが削除されます。

この問題は、bash-3およびbash-4を使用するLinuxおよびSolarisに存在するため、libcのバグのあるパターンマッチャーまたは誤って構成されたロケール定義が原因のバグではありません。

この奇妙で危険な動作は意図されているのですか、それとも何年もの間修正されていないままのバグにすぎませんか?

43
schily

[a-z]のような範囲式を使用する場合、LC_COLLATEの設定によっては、他のケースの文字が含まれる場合があることに注意してください。

LC_COLLATEは、パス名展開の結果を並べ替えるときに使用される照合順序を決定し、範囲式、等価クラス、およびパス名展開とパターンマッチング内の照合シーケンスの動作を決定する変数です。


以下を検討してください。

$ touch a A b B c C x X y Y z Z
$ ls
a  A  b  B  c  C  x  X  y  Y  z  Z
$ echo [a-z] # Note the missing uppercase "Z"
a A b B c C x X y Y z
$ echo [A-Z] # Note the missing lowercase "a"
A b B c C x X y Y z Z

コマンドecho [a-z]が呼び出されると、期待される出力はすべて小文字のファイルになります。また、echo [A-Z]では、大文字のファイルが想定されます。


en_USなどのロケールを使用した標準の照合順序には、次の順序があります。

aAbBcC...xXyYzZ
  • azの間([a-z]内)は、Zを除くすべて大文字です。
  • AZの間([A-Z]内)は、aを除いてすべて小文字です。

見る:

     aAbBcC[...]xXyYzZ
     |              |
from a      to      z

     aAbBcC[...]xXyYzZ
      |              |
from  A     to       Z

LC_COLLATE変数をCに変更すると、期待どおりに見えます。

$ export LC_COLLATE=C
$ echo [a-z]
a b c x y z
$ echo [A-Z]
A B C X Y Z

つまり、これはバグではないバグであり、照合順序の問題です。


範囲式の代わりに、POSIXで定義された 文字クラス を使用できます(upperlowerなど)。これらは、異なるLC_COLLATE構成でも機能し、アクセント付き文字でも機能します。

$ echo [[:lower:]]
a b c x y z à è é
$ echo [[:upper:]]
A B C X Y Z
67
chaos

bash内の[A-Z]は、Dszの後にソートされ、Aの前にソートされるすべての照合要素(文字、ただしハンガリー語のロケールではZのような文字シーケンスとも呼ばれます)に一致します。ロケールでは、cはおそらくBとCの間でソートされます。

$ printf '%s\n' A a á b B c C Ç z Z Ẑ | sort
a
A
á
b
B
c
C
Ç
z
Z
Ẑ

したがって、cまたはz[A-Z]と一致しますが、またはaとは一致しません。

$ printf '%s\n' A a á b B c C Ç z Z Ẑ |
pipe>  bash -c 'while IFS= read -r x; do case $x in [A-Z]) echo "$x"; esac; done'
A
á
b
B
c
C
Ç
z
Z

Cロケールでは、順序は次のようになります。

$ printf '%s\n' A a á b B c C Ç z Z Ẑ | LC_COLLATE=C sort
A
B
C
Z
a
b
c
z
Ç
á
Ẑ

したがって、[A-Z]は、ABCZには一致しますが、Çには一致せず、には一致しません。

(任意のスクリプトで)大文字で照合する場合は、代わりに[[:upper:]]を使用できます。 bashには、latinスクリプト内の大文字のみを一致させる組み込みの方法はありません(個別にリストする場合を除く)。

AZ英語文字に発音区別符号なしで一致させる場合は、[A-Z]または[[:upper:]]を使用できますが、Cロケールで使用できます(データがBIG5などの文字セットでエンコードされていない場合)またはGB18030には、エンコーディングにそれらの文字のエンコーディングが含まれている)複数の文字があるか、それらを個別にリストします([ABCDEFGHIJKLMNOPQRSTUVWXYZ])。

シェルにはいくつかのバリエーションがあることに注意してください。

zshbash -O globasciiranges(bash-4.3で導入された奇妙な名前のオプション)の場合、schily-shyash[A-Z]は、コードポイントがAZのコードポイントの間にある文字と一致するため、Cロケールのbashの動作と同等になります。 。

Ash、mksh、および古代のシェルの場合、上記のzshと同じですが、1バイト文字セットに制限されます。つまり、たとえばUTF-8ロケールでは、[É-Ź]Óとは一致しませんが、[<c3><89>-<c5><b9>]であるため、バイト値0x89〜0xc5と一致します。

ksh93は、両端が小文字または大文字で始まる特殊な範囲として扱われることを除いて、bashのように動作します。その場合、それらの端の間でソートする照合要素のみに一致しますが、それは(または複数文字の照合要素の最初の文字)また、小文字(または大文字)です。したがって、[A-Z]Éでは一致しますが、eeAの間で並べ替えられますが、ZAのように大文字ではないため、Zでは一致しません。

fnmatch()パターン(find -name '[A-Z]'など)またはシステムの正規表現(grep '[A-Z]'など)の場合、システムとロケールによって異なります。たとえば、ここのGNUシステムでは、[A-Z]en_GB.UTF-8ロケールのxでは一致しませんが、th_TH.UTF-8ロケールでは一致します。それを決定するためにどの情報を使用するかは不明ですが、 LC_COLLATEロケールデータから派生したルックアップテーブルに基づいているようです )。

POSIXは、Cロケール以外のロケールでは指定されていない範囲の動作を残すため、すべての動作がPOSIXで許可されています。次に、各アプローチの利点について議論します。

bashのアプローチは、[C-G]と同様に、CGの間に文字を配置する必要があるため、非常に理にかなっています。そして、in-betweenが何であるかを決定するためにユーザーのソート順を使用することが最も論理的なアプローチです。

今問題は、それが多くの人々、特にUnicode以前の伝統的な振る舞いに慣れていた人々、さらには国際化以前の人々の期待を壊してしまうことです。通常のユーザーからは、hの文字がhCの間にあるため、[C-I]Iが含まれ、[A-g]Zが含まれていないことは、何十年もASCIIを扱ってきた人にとっては別の問題です。

そのbashの動作は、GNU正規表現(grep/sed...など)またはfnmatch()のような[A-Z]のような他のGNUツールでのfind -name範囲マッチングとは異なります。

また、[A-Z]が一致するものは、環境、OS、OSのバージョンによって異なることも意味します。 [A-Z]がmatchesに一致するがnotには一致しないという事実も最適ではありません。

zsh/yashの場合、別のソート順を使用します。ユーザーの文字順序の概念に依存する代わりに、文字ポイントコード値を使用します。これには理解しやすいという利点がありますが、ASCII以外の実用的な点では、あまり役に立ちません。 [A-Z]は26の米国英語の大文字に一致し、[0-9]は10進数に一致します。一部のアルファベットの順序に従うUnicodeのコードポイントがありますが、それは一般化されておらず、同じスクリプトを使用する別の人がとにかく文字の順序に必ずしも同意しないため、一般化することはできません。

従来のシェルとmksh、dashの場合、壊れていますが(ほとんどの人がマルチバイト文字を使用しているため)、主な理由は、マルチバイトサポートがまだないためです。 bashzshなどのシェルにマルチバイトサポートを追加することは多大な努力を要し、現在も進行中です。 yash(日本語シェル)は、当初からマルチバイトサポートを使用して設計されていました。

ksh93のアプローチには、システムの正規表現またはfnmatch()と一致するという利点があります(または、少なくともGNUシステムでは少なくとも表示されます)。そこでは、[A-Z]には小文字が含まれず、[A-Z]にはÉが含まれます(andは含まれますがŹは含まれません)。 sortまたは一般にstrcoll()の順序と一致していません。

25

これはbashのドキュメント パターンマッチングセクション で意図され、文書化されています。範囲式[X-Y]には、現在のロケールの照合シーケンスと文字セットを使用して、XYの間のすべての文字が含まれます。

LC_ALL=en_US.utf8 bash -c 'case b in [A-Z]) echo yes; esac' 
yes

ご覧のとおり、ben_US.utf8ロケールでAZの間にソートされています。

この動作を防ぐにはいくつかの選択肢があります。

# Setting LC_ALL or LC_COLLATE to C
LC_ALL=C bash -c 'echo [A-Z]*'

# Or using POSIX character class
LC_ALL=C bash -c 'echo [[:upper:]]*'

またはglobasciirangesを有効にします(bash 4.3以降):

bash -O globasciiranges -c 'echo [A-Z]*'
9
cuonglm

新しいAmazon EC2インスタンスでこの動作を観察しました。 OPは [〜#〜] mcve [〜#〜] を提供していなかったため、投稿します。

$ cd $(mktemp -d)
$ touch foo
$ echo [A-Z]*     # prepare for a surprise!
foo

$ echo $BASH_VERSION
4.1.2(1)-release
$ uname -a
Linux spinup-tmp12 3.14.27-25.47.amzn1.x86_64 #1 SMP Wed Dec 17 18:36:15 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

$ env | grep LC_  # no locale, let's set one
$ LC_ALL=C
$ echo [A-Z]*
[A-Z]*

$ unset LC_ALL    # ok, good. what if we go back to no locale?
$ echo [A-Z]*
foo

したがって、私のLC_*を設定しないと、Linuxのbash 4.1.2(1)リリースが明らかに奇妙な動作をするようになります。それぞれのロケール変数を設定および設定解除することで、奇妙な動作を確実に切り替えることができます。当然のことながら、この動作はエクスポートしても一貫して表示されます。

$ export LC_ALL=C
$ bash
$ echo [A-Z]*
[A-Z]*
$ exit
$ echo $SHLVL
1
$ unset LC_ALL
$ bash
$ echo [A-Z]*
foo

BashがStéphane "Shellshock" Chazelas answered として動作しているのを見ていますが、 パターンマッチングに関するbashのドキュメント はバグがあると思います。

たとえば、indefault Clocale、 '[a-dx-z]'は '[abcdxyz]と同等です」

私はその文(強調は私のもの)を「関連するロケール変数が設定されていない場合、bashはデフォルトでCロケールになります」と読みました。 Bashはそうしているようには見えません。代わりに、文字が分音記号の折りたたみで辞書順にソートされるロケールにデフォルト設定されているように見えます。

$ echo [A-E]*
[A-E]*
$ echo [A-F]*
foo
$ touch "évocateur"
$ echo [A-F]*
foo évocateur

LC_*(具体的にはLC_CTYPELC_COLLATE)が定義されていない場合のbashの動作をドキュメント化しておくとよいでしょう。しかし、その間、私はいくつかのことを共有します 知恵

... [文字の範囲]は、適切に構成しないと期待した結果が得られないため、注意が必要です。現時点では、これらの使用を避け、代わりに文字クラスを使用する必要があります。

そして

あなたが本当に適切で、マルチロケール環境のスクリプトを作成している場合は、ファイルを照合するときにロケール変数が何であるかを確認するか、または完全に一般的な方法。


更新@ G-Manコメントに基づいて、何が起こっているのかをさらに詳しく見てみましょう:

$ env | grep LANG
LANG=en_US.UTF-8

ああ、ハ!これは、前に見た照合を説明しています。すべてのロケール変数を削除してみましょう:

$ unset LANG LANGUAGE LC_ALL
$ env | grep 'LC_|LANG'
$ echo [A-Z]*
[A-Z]*

行きます。現在、bashはこのLinuxシステムのドキュメントに関して一貫して動作しています。ロケール変数のいずれかが設定されている場合(LANGUAGELANGLC_COLLATELC_CTYPELC_ALLなど)、Bashは、そのマニュアル。それ以外の場合、bashはCにフォールバックします。

Wooledge bash FAQ はこう言っています:

最近のGNUシステムでは、変数はこの順序で使用されます。LANGUAGEが設定されている場合は、LANGがCに設定されていない限り、それを使用してください。この場合、LANGUAGEは無視されます。また、一部のプログラムは単にLANGUAGEをまったく使用しないでください。それ以外の場合は、LC_ALLが設定されている場合はそれを使用します。それ以外の場合は、この使用法をカバーする特定のLC_ *変数が設定されている場合はそれを使用します(たとえば、LC_MESSAGESはエラーメッセージをカバーします)。それ以外の場合は、LANGを使用します。

したがって、操作とドキュメントの両方で明らかな問題は、すべてのロケール駆動変数の合計を調べることで説明できます。

6
bishop

ロケールは、[A-Z]で一致する文字を変更できます。使用する

(LC_ALL=C; rm [A-Z]*)

影響を排除します。 (変更をローカライズするためにサブシェルを使用しました)。

3
choroba

すでに述べたように、これは「照合順序」の問題です。

一部のロケールでは、範囲a〜zに大文字が含まれる場合があります。

     aAbBcC[...]xXyYzZ
     |              |
from a      to      z

Bash 4.3以降の正しい解決策は、オプションglobasciirangesを設定することです。

shopt -s globasciiranges

LC_COLLATE=Cglob ingの範囲で設定されているかのようにbashを動作させるには.

2
user79743