bash가 다른 숫자를 제거하는 이유는 무엇입니까?

bash가 다른 숫자를 제거하는 이유는 무엇입니까?

이에 대해(이것은아니요범위로 의도되었지만 명시적인 목록임):

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

Bash는 실수로(IMO) 숫자 ٠١٢٣٤٥٦٧٨٩(두 번째 세트)를 제거했습니다.


문자가 모두 다릅니다(수동 서식 지정).

$ 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

이 웹사이트에서 붙여넣을 때 문제가 발생하지 않도록 하려면 a유니코드 이스케이프를 사용하여 이 유니코드 콘텐츠를 변수로 생성할 수도 있습니다.

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 버전과 일치하도록 업데이트되었습니다. 이 업데이트는 유니코드 문자 정렬에 대한 중요한 개선 사항을 제공합니다.

커밋을 살펴보면 로컬 데이터가 크게 수정되었으므로 아마도 오류가 수정되었을 것입니다!

간단히 말해서, 이 두 기호(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

이제 네 번째 필드는 항상 코드 포인트 자체로 채워집니다. 즉, 처음 몇 개의 필드가 일치하더라도 정의된 정렬 순서를 갖게 됩니다. <U0660>아무런 변경사항이 도입되지 않았지만이 특별한 커밋, 해당 설명은 아이디어를 설명합니다.

[...] 문자의 코드 포인트를 "무시"하는 대신 네 번째 수준에 넣습니다. 이 변경이 없으면 이러한 모든 문자가 동일하게 비교되어 wcscoll 테스트 케이스가 실패하게 됩니다. 이러한 문자의 경우에도 잘 정의된 정렬 순서를 갖는 것이 더 좋으므로 코드 포인트를 순위결정자로 사용하는 것이 더 좋습니다.

  • localedata/locales/iso14651_t1_common: 4개 수준 모두에서 IGNORE가 있는 모든 항목의 경우 IGNORE 대신 네 번째 데이터 정렬 수준에 있는 문자의 코드 포인트를 사용합니다.

이것이 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이 멀티바이트(유니코드)에 대한 지원을 도입했기 때문입니다. 이는 궁극적으로 이 모든 코드를 리팩터링하고 아마도 문제와 관련된 로케일 인식 비교를 사용했을 것입니다.

고쳐 쓰다:질문은 ~이야오류로 보고bash 프로젝트에@ISAAC.


해결책:glibc 2.28을 사용하여 배포판으로 업그레이드하는 것이 불가능할 경우 가능한 해결 방법은 코드 포인트가 동일하게 정렬되지 않는 "간단한" 정렬 순서를 사용하거나 정의하는 것 LC_COLLATE=C.utf8입니다 . POSIX.utf8문제가 데이터 정렬에 있다는 점을 고려하면 LC_COLLATE설정하는 것만으로도 충분합니다. Ubuntu 17.10 및 18.04에서 이 해결 방법을 테스트한 결과 문제를 해결하는 데 충분하다는 것이 나타났습니다.

관련 정보