bashが他の数字を削除するのはなぜですか?

bashが他の数字を削除するのはなぜですか?

これについて(これはいいえ範囲として意図されているが明示的なリストです):

$ a='0123456789 ٠١٢٣٤٥٦٧٨٩ ۰۱۲۳۴۵۶۷۸۹ ߀߁߂߃߄߅߆߇߈߉ ०१२३४५६७८९'
$ echo "${a//[0123456789]}"
  ۰۱۲۳۴۵۶۷۸۹ ߀߁߂߃߄߅߆߇߈߉ ०१२३४५६७८९

Bashは誤って(IMO)番号٠١٢٣٤٥٦٧٨٩(2番目のセット)を削除しました。


すべての文字が異なります(手動で書式設定)。

$ for c in $(echo "$a" | grep -o .); do printf '\\U%04x ' "'$c"; done; echo
\U0030 \U0031 \U0032 \U0033 \U0034 \U0035 \U0036 \U0037 \U0038 \U0039
\U0660 \U0661 \U0662 \U0663 \U0664 \U0665 \U0666 \U0667 \U0668 \U0669
\U06f0 \U06f1 \U06f2 \U06f3 \U06f4 \U06f5 \U06f6 \U06f7 \U06f8 \U06f9
\U07c0 \U07c1 \U07c2 \U07c3 \U07c4 \U07c5 \U07c6 \U07c7 \U07c8 \U07c9
\U0966 \U0967 \U0968 \U0969 \U096a \U096b \U096c \U096d \U096e \U096f

それぞれ対応:

123456789    # Hindu-Arabic Arabic numerals
٠١٢٣٤٥٦٧٨٩   # ARABIC-INDIC
۰۱۲۳۴۵۶۷۸۹   # EXTENDED ARABIC-INDIC/PERSIAN
߀߁߂߃߄߅߆߇߈߉  # NKO DIGIT
०१२३४५६७८९   # DEVANAGARI

このウェブサイトで貼り付けたときに問題が発生しないようにするには、aUnicodeエスケープを使用してこのUnicodeコンテンツを変数として生成することもできます。

a=$(echo -e '\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039 \u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669 \u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9 \u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6\u07c7\u07c8\u07c9 \u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f')

または、エスケープされた文字列を許可するには、直接使用してください$'...'

a=$'\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039 \u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669 \u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9 \u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6\u07c7\u07c8\u07c9 \u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f'

他のシェルはbashのようには機能しません(手動で書式設定)。

$ for sh in zsh ksh lksh mksh bash; do $sh -c 'a="0123456789 ٠١٢٣٤٥٦٧٨٩ ۰۱۲۳۴۵۶۷۸۹ ߀߁߂߃߄߅߆߇߈߉ ०१२३४५६७८९"; echo "$0 : ${a//[0123456789]}" $sh'; done
zsh  :  ٠١٢٣٤٥٦٧٨٩ ۰۱۲۳۴۵۶۷۸۹ ߀߁߂߃߄߅߆߇߈߉ ०१२३४५६७८९
ksh  :  ٠١٢٣٤٥٦٧٨٩ ۰۱۲۳۴۵۶۷۸۹ ߀߁߂߃߄߅߆߇߈߉ ०१२३४५६७८९
lksh :  ٠١٢٣٤٥٦٧٨٩ ۰۱۲۳۴۵۶۷۸۹ ߀߁߂߃߄߅߆߇߈߉ ०१२३४५६७८९
mksh :  ٠١٢٣٤٥٦٧٨٩ ۰۱۲۳۴۵۶۷۸۹ ߀߁߂߃߄߅߆߇߈߉ ०१२३४५६७८९
bash :   ۰۱۲۳۴۵۶۷۸۹ ߀߁߂߃߄߅߆߇߈߉ ०१२३४५६७८९

Bashのソート順序は次のとおりです。

$ mkdir test1; cd test1; IFS=$' \t\n'
$ touch $(echo "$a" | grep -o .)
$ printf '%s' *; echo
߃߇߆߁߂߅߉߄߀߈0٠०۰1١१۱٢2२۲3٣३۳٤4४۴٥5५۵٦6६۶7٧७۷8٨८۸٩9९۹

$ locale
LANG=en_US.utf8
LANGUAGE=
LC_CTYPE="en_US.utf8"
LC_NUMERIC="en_US.utf8"
LC_TIME="en_US.utf8"
LC_COLLATE="en_US.utf8"
LC_MONETARY="en_US.utf8"
LC_MESSAGES="en_US.utf8"
LC_PAPER="en_US.utf8"
LC_NAME="en_US.utf8"
LC_ADDRESS="en_US.utf8"
LC_TELEPHONE="en_US.utf8"
LC_MEASUREMENT="en_US.utf8"
LC_IDENTIFICATION="en_US.utf8"
LC_ALL=

文字を削除するためにソート順を適用しないようです。

とにかく文字が明示的にリストされているので(IMO)してはいけません。

なぜ?


ここではbash 4.4.12を使用します。ただし、3.0、3.2、4.0、4.1、4.4.23、5.0では失敗しますが、2.0.1と2.0.5では失敗します。 3.0の変更によりこの問題が発生したようです。

ベストアンサー1

Ubuntu 17.10(glibc 2.26)とUbuntu 18.04(glibc 2.27)で問題を再現しましたが、Ubuntu 18.10(glibc 2.28)では修正されたようです。

問題はlocaledata、特にen_US.utf8のLC_COLLATEデータにあります。 (実際には、この組み合わせデータはほとんどのロケールに含まれるISO 14651ファイルからのものであるため、他のすべてのutf8ロケールにも影響を与える可能性があります。)

localeddataはglibcから来ており、そこにバグがあるようです(ディストリビューションがこのデータをかなりカスタマイズしているため、glibc <2.28を使用する他のディストリビューションにはこの問題はないかもしれません)。

実は、glibc 2.28を発表新機能のリストを開始:

ISO 14651のローカライズデータは、Unicode 9.0.0で提供されているデータと一致する標準の2016 Edition 4バージョンと一致するように更新されました。この更新プログラムは、Unicode文字の並べ替えに対する重要な改善を提供します。

コミットを見てみると、ローカルデータが大幅に修正されたので、おそらくエラーが修正されたでしょう!

簡単に言えば、これら2つの記号(U0030、つまり「0」、U0660、つまりアラビア語 - インドア0「٠」)の順序による問題は、strcoll(3)、この短いテストでデモできます(sortビハインドストーリーで使用されます)。strcoll

ubuntu-18.04$ { echo 0; echo -e '\u0660'; echo 0; } | sort
0
٠
0

glibc 2.28では:

ubuntu-18.10$ { echo 0; echo -e '\u0660'; echo 0; } | sort
0
0
٠

ご覧のとおり、以前のglibcでは、 "0"の前後にアラビア語 - インダス0 "٠"の順序を変更しません。これは、それらが等しく整列したことを証明します。

glibcソースコードを見ると、問題が発生する理由を理解できます。

内部にISO 14651 glibc 2.27ソース、次の定義を見つけることができます。

<U0030> <0>;<BAS>;<MIN>;IGNORE # 171 0
<U0660> <0>;<BAS>;<MIN>;IGNORE
<U06F0> <0>;<PCL>;<MIN>;IGNORE
<U0966> <0>;"<BAS><NUM>";"<MIN><MIN>";IGNORE

したがって、 '0'( \u0030) と '٠'( \u0660) の両方がまったく同じシーケンス ( <0>;<BAS>;<MIN>;IGNORE) に展開されます。strcollこれは同じ方法で処理されることを意味します。 (これはまた拡張が違うので他のキャラクターが好き\u06f0で影響を受けない理由を説明します。)\u0966

見ているISO 14651 glibc 2.28ソースでは、次の定義を見つけてください。

<U0030> <S0030>;<BASE>;<MIN>;<U0030> % DIGIT ZERO
<U0660> <S0030>;<BASE>;<MIN>;<U0660> % ARABIC-INDIC DIGIT ZERO
<U06F0> <S0030>;<BASE>;<MIN>;<U06F0> % EXTENDED ARABIC-INDIC DIGIT ZERO
<U07C0> <S0030>;<BASE>;<MIN>;<U07C0> % NKO DIGIT ZERO
<U0966> <S0030>;<BASE>;<MIN>;<U0966> % DEVANAGARI DIGIT ZERO

4番目のフィールドは常にコードポイント自体で埋められます。つまり、最初のいくつかのフィールドが一致しても、定義されたソート順を持つことになります。<U0660>何の変更も導入されていないがこの特別なコミット、対応する説明はアイデアを説明します。

[...]文字のコードポイントを「無視」するのではなく、4番目のレベルに置きます。この変更がないと、これらのすべての文字が等しく比較され、wcscollテストケースが失敗します。これらの文字の場合でも、明確に定義されたソート順を持つ方が良いので、コードポイントをランク付け子として使用することをお勧めします。

  • localedata/locales/iso14651_t1_common: 4 つのレベルすべてで IGNORE を持つすべての項目に対して、IGNORE の代わりに 4 番目の照合順序レベルの文字のコード ポイントを使用します。

これがglibc <2.28のlocaledataのバグとglibc 2.28の修正を説明することを願っています。


bashについて見るとソースコード0を使用すると、角かっこ式()内の単一文字()を[0]その文字()で始まり、終わる範囲のように処理することがわかります。[0-0]

cstart = cend = FOLD (cstart);

次に、現在の文字をその範囲と比較します。RANGEMPを使う:

if (RANGECMP (test, cstart, forcecoll) >= 0 && RANGECMP (test, cend, forcecoll) <= 0)
  goto matched;

その後、RANGECMP(rangecmp_wcマルチバイトモードとして定義)wcscollを使う(3)(これはstrcollのマルチバイトバージョンです):

return (wcscoll (s1, s2));

実際、bashは個々の文字の範囲比較を使用するので(範囲を処理するいくつかのコードを共有するショートカット)、生の文字だけでなく、同じ順序で並べられたすべての文字を受け入れます。

他のシェルでは範囲が含まれていない場合は直接比較を実行するため、この問題は発生しない可能性があります。

この問題がbash 3.0で登場し始めた理由は、bash 3.0がマルチバイト(Unicode)のサポートを導入したためです。これは最終的にこれらすべてのコードをリファクタリングし、おそらく問題に関連するロケール認識比較を使用したでしょう。

修正する:質問は〜です。エラーとして報告bashプロジェクトで@ISAAC


解決策:glibc 2.28を使用してディストリビューションにアップグレードすることが不可能な場合、考えられる回避策は、コードポイントが等しくソートされない「簡単な」ソート順序を使用または定義することLC_COLLATE=C.utf8です。POSIX.utf8問題が照合順序にある​​ことを考慮すると、LC_COLLATE設定するだけで十分です。 Ubuntu 17.10および18.04でこの回避策をテストしたところ、問題を解決するのに十分であることがわかりました。

おすすめ記事