sed匹配字符范围

以撒

有没有一种方法可以完全匹配某些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'

但是使用十六进制范围的唯一方法是使用shell将十六进制转换为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做到这一点吗?
  • 如果没有更高的外壳,awk可以用十六进制数执行。

  • 如果可能的话,与sed一起使用的整理序列所匹配的范围是多少sed 's/[^ђ-Ґ]//g'

PS:谢谢,我知道可以在perl中完成。

以撒

在基本sed中,括号表达式中的范围遵循Posix。在Posix中,括号表达式中的范围遵循排序规则。排序顺序被定义为仅在C语言环境中基于字符数字值。但仅适用于单字节值。其余语言环境在Posix中未定义。

要使范围在sed括号表达式中起作用,我们需要使用按数字Unicode代码点(即C.UTF-8)排序的排序规则。但这创建了对utf8中的范围字符进行编码的第二个要求:

  • 获取Unicode代码点范围的字符八进制表示形式(如果使用的语言环境是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
    
  • 添加破折号和\ o以使其在较旧/现在的sed中工作:

    $ 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
    

    请注意,上面我们使用了shell进行转换\u452\u490

给定十六进制Unicode代码点,GNU awk能够生成一个字符串(只要该语言环境有效地允许此类字符):

<<<"$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

底线是需要一个更高的shell(GNU bash或zsh)或awk(仅GNU)。

或使用更高层次的语言(例如perl):

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章