特定のUnicode範囲を正確に一致させる方法はありますか?
使用しようキリル文字範囲例: 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進数をUnicode文字に変換することです。
$ 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'
?
PS:Perlでできることを知っています。ありがとうございます。
答え1
POSIXによると、角括弧式の範囲は、C / POSIXロケールに基づくコードポイントに対してのみ指定されています。他のロケールでは指定されておらず、通常は見つかった照合順序はある程度に基づいています。一部のロケールにはツールがあり、[g-j]
時には一部のチェコロケールにも同じものがあることがわかります。i
ı
ǵ
I
ch
zsh
[x-y]
ロケールに関係なく、コードポイントに基づいて範囲が決定されるまれな範囲の1つです。シングルバイト文字セットの場合、これはバイト値に基づいており、マルチバイト文字セットの場合は、Unicodeコードポイントまたはシステムが表示するために使用するすべての項目に基づいています。ワイド文字内部的にはと共同です mbstowc()
。 API(通常はUnicode)。
だからではzsh
、
[[ $char = [$'\u452'-$'\u490'] ]]
[[ $char = [^ђ-Ґ] ]]
y=${x//[^ђ-Ґ]/}
ロケールの文字セットがマルチバイトであり、これらの2文字を含む場合は、そのUnicode範囲の文字を一致させることができます。これらの文字の一部を含む単一バイト文字セット(たとえば、ほとんどの文字がU + 0401 ... U + 045FにあるISO8859-5)がありますが、これらの文字が使用されるロケールでは範囲がバイト値[ђ-Ґ]
(Unicodeコードポイントではなく、文字セットの対応するコードポイント)に基づいています。
Cロケールでは、範囲はコードポイントに基づいていますが、Cロケールの文字セットには次のものが含まれていることだけが保証されています。ポータブル文字セットこれはPOSIXまたはCコードを書くために必要ないくつかの文字です(これはキリル文字では見つかりません)。また保証されますシングルバイトしたがって、Unicodeで指定されたすべての文字を含めることはできません。実際に最も一般的に使用されるのはASCIIです。
実際には、C(または少なくとも1バイトの文字セットを持つロケール)に設定しない限り、LC_COLLATE
Cに設定することはできません。LC_CTYPE
ただし、多くのシステムにはC.UTF-8
ここで使用できるロケールがあります。
UTF-8は、すべてのUnicode文字とすべての文字セットのすべての文字を表すことができる文字セットの1つです。だからあなたはこれを行うことができます:
< file iconv -t utf-8 |
LC_ALL=C.UTF-8 sh -c 'sed "$(printf "s/[^\321\222-\322\220]//g")"' |
iconv -f utf-8
1つ目は、iconv
ユーザーのロケール文字セットをそれぞれU + 0452およびU + 0490のUTF-8\321\222
および\322\220
UTF-8エンコーディングに変換し、2つ目は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")"
GNUsed
が提供する環境では、$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'
マルチバイト文字セット(Unicodeのワイド文字表現ベースのシステムを含む)を使用するロケールの場合、別のオプションは次のように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文字ではありません。スクリプト、キリル文字を文字として使用する言語は言うまでもありません。文字のリストはUnicodeのバージョンによって変わります。
Debian に似たシステムでは、以下を使用してリストを取得できます。
unicode --max 0 cyrillic
(Ubuntu 16.04では435個、Debian sidでは444個が与えられました(おそらく別のバージョンのUnicodeを使用したでしょう)。
で、、...perl
を参照してUnicodeブロックを一致させ、そのバージョンが使用しているUnicodeバージョンに現在割り当てられているキリル文字の文字を一致させます(例を参照)。\p{Block: Cyrillic}
\p{Block: Cyrillic_Ext_A,B,C}
\p{Block: Cyrillic_Supplement}
\p{Cyrillic}
perl
perl -MUnicode::UCD -le 'print Unicode::UCD::UnicodeVersion'
だから:
perl -Mopen=locale 's/\P{Cyrillic}//g'
答え2
デフォルトのsedでは、角括弧式の範囲はPosixに従います。 Posixでは、括弧内の範囲は組み合わせ規則に従います。照合順序は、Cロケールでのみ文字値に基づいて定義されます。ただし、シングルバイト値でのみ機能します。残りのロケールは Posix では定義されません。
sed 角括弧式内で範囲が機能するには、数値 Unicode コード ポイント (C.UTF-8) に基づいてソートする照合順序を使用する必要があります。ただし、これにより、UTF8で範囲文字をエンコードする必要がある2番目の要件が生成されます。
Unicodeコードポイント範囲の文字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進Unicodeコードポイントを持つ文字列を生成できます(有効なロケールでこれらの文字を許可する場合)。
<<<"$a" awk 'BEGIN{for(i=0x452;i<=0x490;i++){r=r sprintf("%c", i)}}
{gsub("[^" range "]", "")}1'
現在のロケールがUnicodeコードポイント番号にこれらのUnicodeコードポイントを含まない場合は、そのコードポイントを含むことが知られているロケールに変換し、一致するロケール環境変数を使用する必要があります。例:
<<<"$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'