sed가 문자 범위와 일치합니다.

sed가 문자 범위와 일치합니다.

특정 유니코드 범위를 정확하게 일치시키는 방법이 있습니까?
사용하자키릴 문자 범위예: U+400 ~ U+52f

다음을 사용하여 전체 문자 범위(bash 또는 zsh에서)를 인쇄할 수 있습니다.

$ echo -e $(printf '\\U%x' $(seq 0x400 0x52f)) ЀЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяѐёђѓєѕіїјљњћќѝўџѠѡѢѣѤѥѦѧѨѩѪѫѬѭѮѯѰѱѲѳѴѵѶѷѸѹѺѻѼѽѾѿҀҁ҂҃҄҇ҊҋҌҍҎҏҐґҒғҔҕҖҗҘҙҚқҜҝҞҟҠҡҢңҤҥҦҧҨҩҪҫҬҭҮүҰұҲҳҴҵҶҷҸҹҺһҼҽҾҿӀӁӂӃӄӅӆӇӈӉӊӋӌӍӎӏӐӑӒӓӔӕӖӗӘәӚӛӜӝӞӟӠӡӢӣӤӥӦӧӨөӪӫӬӭӮӯӰӱӲӳӴӵӶӷӸӹӺӻӼӽӾӿԀԁԂԃԄԅԆԇԈԉԊԋԌԍԎԏԐԑԒԓԔԕԖԗԘԙԚԛԜԝԞԟԠԡԢԣԤԥԦԧԨԩԪԫԬԭԮԯ

$ a=$(zsh -c 'echo -e $(printf '\''\\U%x'\'' $(seq 0x400 0x52f))')

특정 범위에 대해 필터링하려면 0x452 ~ 0x490을 사용하겠습니다. 예상되는 출력은 다음과 같습니다.

$ b=$(bash -c 'echo -e $(printf '\''\\U%x'\'' $(seq 0x452 0x490))')
$ echo "$b"
ђѓєѕіїјљњћќѝўџѠѡѢѣѤѥѦѧѨѩѪѫѬѭѮѯѰѱѲѳѴѵѶѷѸѹѺѻѼѽѾѿҀҁ҂҃҄҇ҊҋҌҍҎҏҐ
$ echo "$b" | xxd
00000000: d192 d193 d194 d195 d196 d197 d198 d199  ................
00000010: d19a d19b d19c d19d d19e d19f d1a0 d1a1  ................
00000020: d1a2 d1a3 d1a4 d1a5 d1a6 d1a7 d1a8 d1a9  ................
00000030: d1aa d1ab d1ac d1ad d1ae d1af d1b0 d1b1  ................
00000040: d1b2 d1b3 d1b4 d1b5 d1b6 d1b7 d1b8 d1b9  ................
00000050: d1ba d1bb d1bc d1bd d1be d1bf d280 d281  ................
00000060: d282 d283 d284 d285 d286 d287 d288 d289  ................
00000070: d28a d28b d28c d28d d28e d28f d290 0a    ...............

하지만 sed로 필터링하는 것은 불가능해 보입니다. 이것은 작동하지 않습니다:

$ echo "$a" | sed 's/[^\x452-\x490]//g'

이것도 마찬가지입니다(결과가 다른 문자와 일치합니다(아마도 대조 문제).)

$ echo "$a" | sed $'s/[^\u452-\u490]//g' АБВГжзийклмнопрстуфхцчшщъыьэюяёђєѕіїјљњћќѝўџҋҍҏҐҗҙқҝҟҡңҥҧҩҫҭүұҳҵҷҹһҽҿӂӄӆӈӊӌӎӐӒӔӝӟӡӣӥӧөӫӭӯӱӳӵӹԅԇԉԋԍԏ

심지어 이것도 아닙니다(동일한 데이터 정렬 문제):

$ echo "$a" | sed 's/[^ђ-Ґ]//g'

이것은 awk와 함께 작동합니다.

$ echo "$a" | awk '{gsub(/[^ђ-Ґ]/,"")}1'

그러나 16진수 범위를 사용하는 유일한 방법은 셸을 사용하여 16진수를 유니코드 문자로 변환하는 것입니다.

$ echo "$a" | awk $'{gsub(/[^\u452-\u490]/,"")}1'

또는 (두 솔루션 모두):

$ c=$(bash -c 'printf "\u452-\u490"') 
$ echo "$a" | awk '{gsub(/[^'"$c"']/,"")}1'
$ echo $a | awk -v ra="[^$c]" '{gsub(ra,"")}1'

질문:

  • sed를 사용하여 이를 수행할 수 있는 방법이 있습니까?
  • 더 높은 쉘 없이도 16진수로 이 작업을 수행할 수 있습니까?

  • 가능하다면 sed가 사용된 데이터 정렬 순서와 일치하는 범위는 정확히 무엇입니까 sed 's/[^ђ-Ґ]//g'?

추신: Perl로 할 수 있다는 것을 알고 있습니다. 감사합니다.

답변1

POSIX에 따르면 대괄호 표현식의 범위는 C/POSIX 로케일을 기반으로 하는 코드 포인트에 대해서만 지정됩니다. 다른 로케일에서는 지정되지 않으며 일반적으로 찾은 데이터 정렬 순서를 어느 정도 기반으로 합니다. 일부 로케일에서는 도구에 따라 가 있고 [g-j]때로는 일부 체코 로케일에도 같은 것이 있다는 것을 알 수 있습니다.iıǵIch

zsh[x-y]로케일에 관계없이 코드 포인트를 기반으로 범위가 결정되는 드문 범위 중 하나입니다 . 단일 바이트 문자 집합의 경우 이는 바이트 값을 기반으로 하며, 다중 바이트 문자 집합의 경우 유니코드 코드 포인트 또는 시스템이 표시하는 데 사용하는 모든 항목을 기반으로 합니다.와이드 문자내부적으로는 와 공동입니다 mbstowc(). API(보통 유니코드).

그래서 에서는 zsh,

  • [[ $char = [$'\u452'-$'\u490'] ]]
  • [[ $char = [^ђ-Ґ] ]]
  • y=${x//[^ђ-Ґ]/}

로케일의 문자 집합이 멀티바이트이고 이 두 문자를 포함하는 경우 해당 유니코드 범위의 문자를 일치시킬 수 있습니다. 이러한 문자 중 일부를 포함하는 단일 바이트 문자 세트(예: 대부분의 문자가 U+0401 .. U+045F 에 있는 ISO8859-5)가 있지만 이러한 문자가 사용되는 로케일에서는 범위 [ђ-Ґ]가 바이트 값(유니코드 코드 포인트가 아닌 문자 세트의 해당 코드 포인트)을 기반으로 합니다.

C 로케일에서 범위는 코드 포인트를 기반으로 하지만 C 로케일의 문자 집합은 다음을 포함하는 것만 보장됩니다.휴대용 문자 세트이는 POSIX 또는 C 코드를 작성하는 데 필요한 몇 개의 문자입니다(이 문자는 키릴 문자에서는 찾을 수 없음). 또한 보장됩니다단일 바이트따라서 유니코드로 지정된 모든 문자를 포함하는 것은 불가능합니다. 실제로 가장 일반적으로 사용되는 것은 ASCII입니다.

실제로 C(또는 최소한 단일 바이트 문자 집합이 있는 로케일)로 설정하지 않고는 LC_COLLATEC로 설정할 수 없습니다 . LC_CTYPE그러나 많은 시스템에는 C.UTF-8여기에서 사용할 수 있는 로캘이 있습니다.

UTF-8은 모든 유니코드 문자와 모든 문자 세트의 모든 문자를 나타낼 수 있는 문자 세트 중 하나입니다. 그래서 당신은 이것을 할 수 있습니다 :

< file iconv -t utf-8 |
  LC_ALL=C.UTF-8 sh -c 'sed "$(printf "s/[^\321\222-\322\220]//g")"' |
  iconv -f utf-8

첫 번째는 iconv사용자의 로케일 문자 세트를 각각 U+0452 및 U+0490의 UTF-8 \321\222\322\220UTF-8 인코딩으로 변환하고, 두 번째는 iconv로케일의 문자 세트로 다시 변환합니다.

현재 로케일이 이미 UTF-8을 문자 세트로 사용하고 file해당 문자 세트로 작성된 경우 다음과 같이 단순화할 수 있습니다.

<file LC_ALL=C.UTF-8 sed 's/[^ђ-Ґ]//g'

또는:

<file LC_ALL=C.UTF-8 sed "$(printf "s/[^\321\222-\322\220]//g")"

GNU sed가 제공하는 환경에서는 $POSIXLY_CORRECT인코딩된 바이트 값을 기반으로 문자를 지정할 수 있습니다.

<file LC_ALL=C.UTF-8 sed 's/[^\321\222-\322\220]//g'

이전 버전에서는 다음이 필요할 수 있습니다.

<file LC_ALL=C.UTF-8 sed 's/[^\o321\o222-\o322\o220]//g'

또는 16진수 변형:

<file LC_ALL=C.UTF-8 sed 's/[^\xd1\x92-\xd2\x90]//g'

멀티바이트 문자 세트(유니코드의 와이드 문자 표현 기반 시스템 포함)를 사용하는 로케일의 경우 또 다른 옵션은 다음 awk과 함께 GNU를 사용하는 것입니다.

awk 'BEGIN{for (i = 0x452; i<=0x490; i++) range = range sprintf("%c", i)}
     {gsub("[^" range "]", ""); print}'

(처음에는 POSIX에서 GNU awk처럼 동작하려면 awk 구현이 필요하다고 생각했지만, POSIX에서는 다음과 같이 동작 하려면 awk 구현이 sprintf("%c", i)필요하기 때문에 그렇지 않습니다.i코딩로케일의 문자(코드 포인트 아님)입니다. 이는 멀티바이트 문자와 함께 이식 가능하게 사용할 수 없음을 의미합니다.

어쨌든 U+0400 .. U+052F 범위는 키릴 문자의 유일한 유니코드 문자가 아닙니다.스크립트, 키릴 문자를 문자로 사용하는 언어는 말할 것도 없습니다. 문자 목록은 유니코드 버전에 따라 변경됩니다.

데비안과 유사한 시스템에서는 다음을 사용하여 목록을 얻을 수 있습니다.

unicode --max 0 cyrillic

(Ubuntu 16.04에서는 435개, Debian sid에서는 444개가 주어졌습니다(아마도 다른 버전의 유니코드를 사용했을 것입니다).

에서 , , ... perl를 참조 하여 유니코드 블록을 일치시키고 해당 버전이 사용 중인 유니코드 버전에 현재 할당된 키릴 문자의 문자를 일치시킵니다 ( 예 참조).\p{Block: Cyrillic}\p{Block: Cyrillic_Ext_A,B,C}\p{Block: Cyrillic_Supplement}\p{Cyrillic}perlperl -MUnicode::UCD -le 'print Unicode::UCD::UnicodeVersion'

그래서:

perl -Mopen=locale 's/\P{Cyrillic}//g'

답변2

기본 sed에서 대괄호 표현식의 범위는 Posix를 따릅니다. Posix에서 괄호 안에 있는 범위는 조합 규칙을 따릅니다. 데이터 정렬은 C 로케일에서만 문자 값을 기준으로 정의됩니다. 그러나 단일 바이트 값에만 작동합니다. 나머지 로케일은 Posix에서 정의되지 않습니다.

sed 대괄호 표현식 내에서 범위가 작동하려면 숫자 유니코드 코드 포인트(C.UTF-8)를 기준으로 정렬하는 데이터 정렬을 사용해야 합니다. 그러나 이로 인해 UTF8로 범위 문자를 인코딩해야 하는 두 번째 요구 사항이 생성됩니다.

  • 유니코드 코드 포인트 범위의 문자 8진수 표현을 가져옵니다(사용된 로케일이 UTF-8인 경우):

    $ printf '\u452\u490' | od -An -to1
    

    로캘이 UTF-8이 아닌 경우 값을 UTF-8로 변환합니다.

    $ printf '\u452\u490' | iconv -t utf-8 | od -An -to1
    321 222 322 220
    
  • 이전/현재 sed에서 작동하도록 대시와 \o를 추가하세요.

    $ printf '\o%s\o%s-\o%s\o%s' $(printf '\u452\u490'|iconv -tutf-8|od -An -to1)
    \o321\o222-\o322\o220
    
  • 이 범위를 사용하면 sed에서 사용할 수 있습니다.

    $ echo "$a" | LC_ALL=C.UTF-8 sed 's/[^\o321\o222-\o322\o220]//g'
    
  • 그러나 로케일이 C.UTF-8이고 주어진 문자열이 utf8로 인코딩되어 사용된 로케일로 다시 변환되는지 확인하십시오.

    $ echo "$a" | iconv -t utf-8 |
                  LC_ALL=C.UTF-8 sed 's/[^\o321\o222-\o322\o220]//g' |
                                    iconv -f utf-8
    

    노트위에서 우리는 쉘을 사용하여 \u452\u490.

GNU awk는 16진수 유니코드 코드 포인트가 있는 문자열을 생성할 수 있습니다(유효한 로캘에서 이러한 문자를 허용하는 경우).

<<<"$a" awk 'BEGIN{for(i=0x452;i<=0x490;i++){r=r sprintf("%c", i)}}
 {gsub("[^" range "]", "")}1'

현재 로케일이 유니코드 코드 포인트 번호에 이러한 유니코드 코드 포인트를 포함하지 않는 경우 해당 코드 포인트를 포함하는 것으로 알려진 로케일로 변환하고 일치하는 로케일 환경 변수를 사용해야 합니다. 예:

<<<"$a" iconv -t utf8 |  
LC_ALL=en_US.UTF-8 awk '
        BEGIN{for(i=0x452;i<=0x490;i++){r=r sprintf("%c", i)}}
        {gsub("[^" r "]", "")}1
        ' | iconv -f utf8

결론최신 셸(GNU bash 또는 zsh) 또는 awk(GNU만 해당)가 필요합니다.

또는 Perl과 같은 더 높은 수준의 언어를 사용하십시오.

$ echo "$a" | perl -Mopen=locale -ane 's/[^\x{452}-\x{490}]//g; print'

관련 정보