[AZ]がbashの小文字と一致するのはなぜですか?

[AZ]がbashの小文字と一致するのはなぜですか?

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

この問題はbash-3とbash-4を使用してLinuxとSolarisに存在するため、libcの不良パターンマッチングや誤って設定されたロケール定義によって発生したバグである可能性はありません。

この奇妙で危険な行動は意図的なものですか、それとも長年にわたって存在してきた修正されていないバグですか?

答え1

[az]などの範囲表現を使用している場合は、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
  • aを除くすべての大文字は、z(in)の間にあります。[a-z]Z
  • AおよびZ(in)[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 定義の範囲式を使用できます。キャラクタークラスupperまたはlower。また、さまざまな構成で動作し、LC_COLLATE使用することもできます。アクセント文字:

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

答え2

[A-Z]inは、ソートされ、事前にソートされたすべてのbash要素(文字ですが、ハンガリー語ロケールのように文字シーケンスとも呼ばれます)と一致します。あなたの地域では、おそらくBとCの間になります。DszAZc

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

したがってc、orはz一致します[A-Z]が、orは一致しません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大文字のみを一致させる組み込み方法はありません。ラテン語スクリプト(別途記載されていない場合)

A合わせたいならZ 英語[A-Z]発音区別符号を持たない文字の場合、または[[:upper:]]inを使用できますC(データが複数の文字のエンコードを持つBIG5またはGB18030などの文字セットでエンコードされていないと仮定)。含むその文字のコード)または個別にリストします([ABCDEFGHIJKLMNOPQRSTUVWXYZ])。

シェル間には若干の違いがあります。

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

ash、mksh、およびAncientシェルの場合、上記とzsh同じですが、シングルバイト文字セットに制限されます。つまり、たとえばUTF-8ロケールでは[É-Ź]一致項目がないÓが、そのため[<c3><89>-<c5><b9>]バイト値0x89から0xc5まで一致します!

ksh93bashすべて小文字または大文字で終わる特殊なケース範囲を処理することを除いて、次のように機能します。この場合、この末尾の間にソートされた組み合わせ要素のみが一致しますが、その要素(または複数文字の組み合わせ要素の最初の文字)返品小文字(またはそれぞれ大文字)。したがって[A-Z]、onは一致しますÉが、onは一致しません。まるでeandのe間を並べ替えるように、andのように大文字で表示しませんAZAZ

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

POSIX は、C ロケール以外のロケールで範囲外の動作を維持するため、POSIX ではすべての動作を許可します。今、私たちは各アプローチの利点について議論することができます。

bash[C-G]との間の文字が欲しいので、このアプローチは意味があります。ユーザーのソート順序を使用して、何を決定するかを決定します。CG真ん中最も論理的なアプローチです。

今の問題は、これが多くの人々の期待を破るということです。特に、Unicodeの移転や国際化の前の伝統的な行動に慣れている人にとっては、そうです。一般ユーザーにとっては、文字がインクルードの間に含まれていないため、含めることは合理的かも[C-I]しれませんが、数十年にわたってASCIIだけを扱ってきた人にとっては別の話です。hhCI[A-g]Z

このbash動作は[A-Z]GNU 正規表現 (例: grep/ sed...) またはfnmatch().find -name

これはまた、[A-Z]環境、オペレーティングシステム、およびオペレーティングシステムのバージョンによって一致する項目が異なる可能性があることを意味します。 Áが一致するがŹが一致しないという事実[A-Z]も次善策です。

zsh/ の場合は、yash別のソート順を使用します。ユーザーの文字順序の概念に頼るのではなく、文字ポイントコード値を使用します。これは理解しやすいという利点がありますが、非常に実用的な観点からはASCII以外にはあまり役に立ちません。[A-Z]26 個の米国英語大文字と一致し、[0-9]小数点以下の桁数と一致します。 Unicodeには特定のアルファベット順に従ういくつかのコードポイントがありますが、これは普遍的ではなく、同じスクリプトを使用する他の人が必ずしもアルファベット順に同意する必要はないため、普遍的ではありません。

従来のシェルとmkshの場合、ダッシュは破損していますが(最近、ほとんどの人はマルチバイト文字を使用しています)、ほとんどマルチバイトをサポートしていないためです。 WindowsやLinuxbashなどのシェルにマルチバイトサポートを追加するzshために多くの努力が行われており、まだ進行中です。yash(日本語シェル)はもともと最初からマルチバイトをサポートするように設計されています。

ksh93のアプローチは、システムの正規表現やfnmatch()と一貫性を維持するという利点があります(または少なくともGNUシステムではそうです)。そこには[A-Z]小文字が含まれておらず(そしてÁは[A-Z]含まれÉていますがŹは含まれていません)、一部の人々の期待を破りません。正しくないか、sort一般的な順序ではありませんstrcoll()

答え3

その意図とbash文書に文書化されており、パターンマッチング部。範囲式には、現在のロケールの組み合わせ順序と文字セットの間、およびそれを使用する[X-Y]すべての文字が含まれます。XY

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

ロケール間およびロケール内でソートが発生していることがわかりますbAZen_US.utf8

この動作を防ぐためのいくつかのオプションがあります。

# 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]*'

答え4

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

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

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

関連情報