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


42

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

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

これは奇妙で危険な動作を意図したものですか、それとも長年にわたって修正されていない単なるバグですか?


3
locale出力は何ですか?これを再現することはできません(touch foo; echo [A-Z]*「foo」ではなくリテラルパターンを出力します)。
chepner

4
どれだけ多くの人が自分に効果があると言っているか、またはLC_COLLATEがこれにどのように影響するかの例を示していることを考慮して、質問を編集して、質問しているシナリオを正確に示すサンプルbashセッションを追加できます。使用しているbashバージョンを含めてください。
ケンスター

ここですべてのテキストを読んだ場合、使用しているbashバージョンと、すでに私の質問に対する解決策を投稿してから何をしたかがわかります。ソリューションを繰り返しましょう。bashは独自のロケールを管理しないため、新しい環境で別のbashプロセスを開始するまでLC_COLLATEを設定しても何も変更されません。
気味悪い

1
参照してくださいLC_COLLATE文字範囲に影響を与える(必要がある)んか?(しかし、その質問は特にbashについてのものではありませんでした)
ジル 'SO-悪であるのをやめる'

「LC_COLLATEを設定しても、新しい環境で別のbashプロセスを開始するまで何も変わりません。」これは、Solarisのbash-4で見られる動作と一致しません。実行中のシェルの動作を変更しています。 # echo [A-Z]* ; export LC_COLLATE=C ; echo [A-Z]*A b B z ZABZ
BowlOfRed

回答:


67

[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
  • az(in [a-z])の間は、を除くすべて大文字ですZ
  • AZ(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

この動作がLC_ *環境変数によって制御可能である場合、私は尋ねませんでした。私はPOSIX標準委員会で働いており、例えばtr、問題の照合について知っているので、これが最初にチェックしたことです。
気味悪い

@schily古いbash-3とbash-4のどちらでも問題を再現できません。どちらも制御可能LC_COLLATEです。これもマニュアルに記載されています。
カオス

申し訳ありませんが、私はあなたが信じていることを再現することはできませんが、私自身の答えを見ます...この議論のアイデアから、私は問題の理由を発見しました。
気味悪い

25

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

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

そうczによってマッチされるだろう[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文字の大文字のみを照合する組み込みの方法はありません(個別にリストする場合を除く)。

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

シェルには多少の違いがあることに注意してください。

(奇妙という名前のbash-4.3で導入されたオプション)、および、コードポイントの間にある文字に一致していることをとのことなので、の動作に相当しますCロケールインチzshbash -O globasciirangesschily-shyash[A-Z]AZbash

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

ksh93bash両端が小文字または大文字で始まる特別な場合の範囲として扱うことを除いて、同様に動作します。その場合、それらの端の間でソートする照合要素でのみ一致しますが、それは(または複数文字照合要素の最初の文字)小文字(または大文字)でもあります。だから、[A-Z]そこにマッチしますÉが、ないのeeの間でソートしAZけどのように大文字されていないAZ

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

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

bashアプローチは、のように多くの意味になり[C-G]、我々は間に文字をしたい、CG。そして、何が中間にあるかを決定するためにユーザーのソート順を使用することが最も論理的なアプローチです。

現在、問題は多くの人々、特にUnicode以前、国際化以前の伝統的な振る舞いに慣れている人々の期待を破ることです。通常のユーザーからは、文字がからの間であり、を[C-I]含まないことを含むことは理にかなっているかもしれませんが、ASCIIを数十年しか扱っていない人にとっては別の問題です。hhCI[A-g]Z

そのbash行動も異なっている[A-Z](のようにGNU正規表現のように他のGNUツールで範囲マッチングgrep/ sed...)かfnmatch()のようにfind -name

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

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

従来のシェルとmksh、ダッシュの場合、壊れています(今ではほとんどの人がマルチバイト文字を使用しています)が、主にマルチバイトサポートがまだないためです。bashやなどのシェルにマルチバイトサポートを追加するzshことは大きな努力であり、現在も進行中です。yash(日本語のシェル)は当初、最初からマルチバイトをサポートするように設計されていました。

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


1
正しければ、これはLC_ *変数を介して制御できます。別の理由があるようです。
気味悪い

1
@cuonglm、より似ていますmksh(両方ともpdkshから派生)。posh -c $'case Ó in [É-Ź]) echo yes; esac'何も返しません。
ステファンシャゼラス

2
@ schily、globは文字のソート順に基づいているsortため、言及しbashます。現在、そのような古いバージョンのにはアクセスできませんがbash、後で確認できます。その時は違いましたか?
ステファンシャゼラス

1
もう一度お話ししましょう。zsh、POSIX-ksh88、ksh93t + Bourne Shellは、すべて期待どおりに動作します。Bashは異なる動作をする唯一のシェルであり、この場合、bashはロケールを介して制御できません。
気味悪い

2
@schily、文字U + 00FF(それ自体は0xC3 0xBFとしてエンコードされる)ではなく\xFFバイト 0xFF があることに注意してくださいÿ\xFF単独では有効な文字を形成しないため、なぜに一致する必要があるのか​​わかりません[É-Ź]
ステファンシャゼル

9

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

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

あなたは、見ることができるbの間にソートAし、Zen_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]*'

6

新しいAmazon EC2インスタンスでこの動作を観察しました。OPはMCVEを提供しなかったので、私は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_*セットを持っていないと、bash 4.1.2(1)-Linuxでのリリースにつながり、明らかに奇妙な動作を引き起こします。それぞれのロケール変数を設定および設定解除することにより、奇妙な動作を確実に切り替えることができます。当然のことながら、この動作はエクスポートを通じて一貫しているように見えます。

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

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

たとえばデフォルトのCロケールでは、「[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はCにフォールバックします。

Wooledge bashのよくある質問は、この言うことがあります。

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

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


LC_variableが存在せず、bashがCロケールに記載されているとおりに動作しない場合、これはバグです。
schily

1
@bishop:(1)タイプミス:MVCEはMCVEである必要があります。(2)サンプルを完成させたい場合は、env | grep LANGまたはを追加する必要がありecho "$LANG"ます。
G-Manは「Reinstate Monica」と言います

@schilyさらなる調査により、このLinuxシステムのドキュメントや操作にバグはないと確信しました。
ビショップ

@ G-Manありがとう!私は忘れていましたLANG。そのヒントで、すべてが説明されます。
ビショップ

LANGは、1つの変数では不十分であることが判明する前に、1988年頃にSunによって最初のローカライズの試みで導入されました。現在、それはフォールバックとして使用され、LC_ALLは強制上書きとして使用されています。
気味悪い

3

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

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

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


これは機能せず、すべての文字に一致します
15

7
globがrmの実行前に行われたため、これは機能しません。export LC_ALL=C最初に試してください。
クオンルム

申し訳ありませんが、rmではなくbashに関連する質問を誤解しています。
気味悪い

@schily:はい、私は間違っていました、あなたは文を分ける必要があります。アップデートを確認してください。
チョロバ

2

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

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

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

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

shopt -s globasciiranges

bash LC_COLLATE=Cグロブ範囲で設定されているかのように動作するようにします。


-6

私は自分の質問に正しい答えを見つけたようです:

Bashは独自のロケールを管理しないため、バグがあります。そのため、bashプロセスでLC_ *を設定しても、そのシェルプロセスでは効果がありません。

LC_COLLATE = Cを設定してから別のbashを開始すると、グロビングは新しいbashプロセスで期待どおりに機能します。


2
私のどの洗面所にもありません。
カオス

2
私のマシンのbashのどのバージョンでもこれを再現していませんexport。あなたが正しくしなかったようです。
クリスダウン

それで、適切にエクスポートされ、新しいbashプロセスに影響を与えるものは、適切にエクスポートされないと思いますか?
schily

4
Solarisでの環境の処理は悪名高いため、bashの「バグ」がSolaris固有の回避策の欠如であったとしても驚かないでしょう。
ホッブズ

1
@schily:シェル内のLC_ *変数を変更して、それ自身のロケール状態を更新する必要がある場合の引用はありますか?私はまったく反対だと思います。特に、スクリプトを実行するシェルの場合、スクリプトの解析/実行の途中でロケールを変更しても、スクリプトはテキストファイルであり、「テキストファイル」はコンテキスト内でのみ意味があるため、明確な動作さえありません。単一文字エンコーディング。
R ..
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.