从字符串中收集字符并打印它们的 unicode

从字符串中收集字符并打印它们的 unicode

语境(如果你不在乎,请跳过;如果你怀疑我完全走错了路,请阅读)

对于内存较小的嵌入式系统,我想生成仅包含实际需要的字形的字体。因此,在构建时,我需要扫描语言文件,从字符串中提取字符并使用它们的代码作为字体生成工具的参数。

包含相关字符串的翻译文件(当然,这只是一个例子,但至少它涵盖了一些 unicode 的东西)

TEXT_1=Foo
TEXT_2=Bar
TEXT_3=Baz
TEXT_4=Ünicødé
TEXT_5=ελληνικά

预期产出

0x42,0x61,0x72,0x42,0x61,0x7A,0x46,0x6F,0x6F,0xDC,0x6E,0x69,0x63,0xF8,0x64,0xE9,0x3B5,0x3BB,0x3BB,0x3B7,0x3BD,0x3B9,0x3BA,0x3AC

到目前为止我的方法

该脚本只是执行我所描述的操作:sed读取文件,提取字符串,并准备将其格式化printfsort -u删除重复项:

for char in $(sed "s/[^=]*=//;s/./'& /g" myLang.translation|sort -u); do
  printf "0x%02X\n" $char
done

这适用于这个例子,但感觉丑陋、不可靠、错误,对于真实文件来说可能很慢,所以你能说出一个更好的工具、更好的方法、更好的东西吗?

答案1

perl

perl -C -lne '
  if (/=(.*)/) {$c{$_}++ for split //, $1}
  END{print join ",", map {sprintf "0x%X", ord$_} sort keys %c}
  ' your-file

给出:

0x42,0x46,0x61,0x63,0x64,0x69,0x6E,0x6F,0x72,0x7A,0xDC,0xE9,0xF8,0x3AC,0x3B5,0x3B7,0x3B9,0x3BA,0x3BB,0x3BD
  • -C如果语言环境使用 UTF-8 作为其字符映射,则执行 UTF-8 I/O
  • -ln sed -n模式,其中代码在输入的每一行上运行。-l从输入中删除行分隔符,并将其添加回输出中(执行 a $\ = $/
  • -e 'code'指定要在命令行而不是从脚本运行的代码。
  • /=(.*)/要匹配的行至少包含一个捕获 (第一个捕获组)=中第一次出现之后的内容。$1
  • split //, $1用空分隔符将其拆分为单个字符
  • $c{$_}++ for that-above循环该字符列表并递增相应的关联数组元素。%c将字符映射到其出现次数。我们在这里不使用该计数。
  • END{code}code只有跑到最后。
  • sort keys %c按词法对该关联数组的键进行排序
  • map { code } @list通过在每个元素上应用代码来转换列表。
  • ord$_获取字符的数值。
  • sprintf "0x%X"将其格式化为十六进制(大写ABCDEF,但小写0x)。
  • join ",", @list加入列表,
  • print打印它,后跟$\(换行符)。

在 zsh 中(可能效率低很多):

$ set -o cbases -o extendedglob
$ LC_COLLATE=C
$ echo ${(j[,])${(ous[])"$(<your-file cut -sd= -f2- | tr -d '\n')"}/(#m)?/$(([#16]#MATCH))}
0x42,0x46,0x61,0x63,0x64,0x69,0x6E,0x6F,0x72,0x7A,0xDC,0xE9,0xF8,0x3AC,0x3B5,0x3B7,0x3B9,0x3BA,0x3BB,0x3BD

或者不使用外部实用程序:

$ set -o cbases -o extendedglob
$ LC_COLLATE=C
$ echo ${(j[,])${(@ous[])${(f)"$(<your-file)"}#*=}/(#m)?/$(([#16]#MATCH))}
0x42,0x46,0x61,0x63,0x64,0x69,0x6E,0x6F,0x72,0x7A,0xDC,0xE9,0xF8,0x3AC,0x3B5,0x3B7,0x3B9,0x3BA,0x3BB,0x3BD
  • "$(<you-file)"文件的内容,删除了尾随换行符,并用引号引起来,因此它不是 IFS 分割的
  • ${(f)param}按行分割f以将行作为列表获取
  • ${array#*=}*=从数组元素中删除最短的前导部分匹配。
  • @标志以确保 列表加工
  • o按词法顺序(基于 C 语言环境中的代码点)
  • unique 删除重复项
  • s[]分裂成单独的字符。
  • ${array/(#m)?/$(([#16]#MATCH))}将感谢中捕获的字符 ( ?)替换为以 16 为基数格式化的值(在算术表达式中)。有了这个选项,就可以代替$MATCH(#m)#MATCH[#16]cbases0xBEEF16#BEEF
  • j[,]加入,.

将其分解为单独的步骤将使其更清晰:

set -o cbases -o extendedglob
LC_COLLATE=C
contents=$(<your-file)
lines=( ${(f)contents} )
values=( ${lines#*=} )
chars=( ${(@ous[])values} )
codepoints=( ${chars/(#m)?/$(( [#16] #MATCH ))} )
echo ${(j[,])codepoints}

答案2

应该可以用iconv | hexdump.

示例输入的简洁概念验证:

cut -d= -f2- | iconv -t UTF-32LE | hexdump -ve '"0x%02X,"'

笔记:如果在 a 上运行,上述命令将按预期工作小尾数法CPU 架构,例如 x86 系列。下面详细介绍了这一警告。

要统一代码点,并去除虚假逗号和 0x0A:

cut -d= -f2- | sed 's/./&\
/g' | sort -u | tr -d '\n' | iconv -t UTF-32LE | hexdump -ve '",0x%02X"' | cut -d, -f2-

笔记:在后一个示例中,该sed命令在其命令的替换部分中嵌入了一个换行符s///,因为我想提供一个更可移植的示例。如果您使用的 shell 支持$'...'文字语法,则可以将整个sed命令替换为sed $'s/./&\\\n/g',以便轻松地将换行符嵌入到同一行中。文字语法通常应可用于支持您在示例中使用的内置函数的转换参数$'...'的版本。bash'<char>%Xprintf

关于此解决方案的一些注意事项:

  • CPU 字节序:因为我们正在输入hexdump,它只能处理 CPU 自身字节序的固定大小整数,因此我们需要iconv转换为与此类要求兼容的字符流。UTF-32LE是整个 Unicode 空间的固定每个字符 4 字节的编码,以小端格式表示。如果您宁愿需要在大端 CPU 上运行该命令,您也可以这样做iconv -t UTF-32BE
  • 上面的例子假设输入是用相同的字符集编码被使用过执行当前语言环境,就像你自己的例子一样。如果您想要不匹配所涉及的编码,您可能会想使用显式指定输入数据的编码-f的选项iconv,但最安全的方法是切换全部的管道到带有与输入数据相同编码的语言环境,因为cut上面的命令需要检测=字符,同时sed需要正确检测每个字符实体
  • 转换可用性:您的主机是否iconv确实能够将输入数据的编码转换为 UTF32 取决于系统。至少,iconv当主机系统上安装了所有“gconv 模块”时,GNU (glibc) 的功能非常强大。对于上面的命令,您至少需要输入数据的编码和 UTF32 编码。请注意,UTF8 通常内置于 glibc 自己的主libc文件中,而 UTF32 风格则位于特定的 gconv 模块的.so文件中。典型的基于 GNU glibc 的成熟操作系统通常带有整套可加载的 gconv 模块,这些模块几乎包含世界上的所有编码。
  • 转换速度:GNUiconv最典型的“三角测量”,即从输入编码转换为iconv自己的内部表示,然后从后者转换为所需的输出编码。据我所知非常非常Fewiconv的 gconv 模块提供从一种编码到另一种编码的直接转换,跳过中间步骤。我不知道这种“三角测量”行为以及cut | sed | sort整个管道执行的辅助转换(等)是否比非 GNU iconv(或根本不)转换更快或更慢。iconv

最后请注意,当然,上面所说的内容都不适合您的目标嵌入式系统。该解决方案应该在通常功能强大且功能齐全的系统上运行。

答案3

使用(以前称为 Perl_6)

从这里: https://docs.raku.org/language/faq.html#String:_How_can_I_get_the_hexadecimal_representation_of_a_string%3F

~$ raku -ne 'BEGIN my %chars; 
             %chars{$_.encode.gist}++ for .split("=", limit => 2, :skip-empty)[1..*].comb; 
             END put .keys for %chars.sort;'  file

#或者

~$ raku -ne 'BEGIN my %chars; 
             %chars{$_.encode.gist}++ for .comb(/<?after TEXT_ \d+ \= > .+ $/).comb; 
             END put .keys for %chars.sort;'  file

ordRaku(又名 Perl_6)具有、ordsuniqueprintf、等功能sprintf,但上面的代码直接改编自文档 - 因此可能是推荐的。

输入示例(底部有额外的空行):

TEXT_1=Foo
TEXT_2=Bar
TEXT_3=Baz
TEXT_4=Ünicødé
TEXT_5=ελληνικά

示例输出:

utf8:0x<42>
utf8:0x<46>
utf8:0x<61>
utf8:0x<63>
utf8:0x<64>
utf8:0x<69>
utf8:0x<6E>
utf8:0x<6F>
utf8:0x<72>
utf8:0x<7A>
utf8:0x<C3 9C>
utf8:0x<C3 A9>
utf8:0x<C3 B8>
utf8:0x<CE AC>
utf8:0x<CE B5>
utf8:0x<CE B7>
utf8:0x<CE B9>
utf8:0x<CE BA>
utf8:0x<CE BB>
utf8:0x<CE BD>

Rakuutf8默认使用,这就是您在上面看到的。但是您可以.encode("utf-16")返回这些.values结果以获得与 @StephaneChazelas 的 Perl(5) 答案相同的结果:

~$ raku -e 'my @a = lines>>.subst(:global, / ^^ <(TEXT_ \d+ \= )> /).join.comb.unique;  \
            print join ",", map {sprintf("0x%X", .encode("utf-16").values) }, @a[].sort;'
0x42,0x46,0x61,0x63,0x64,0x69,0x6E,0x6F,0x72,0x7A,0xDC,0xE9,0xF8,0x3AC,0x3B5,0x3B7,0x3B9,0x3BA,0x3BB,0x3BD

如果需要,下面的第二个链接对 Unicode 规范化(在 Raku 中)进行了相当广泛的讨论。从该页面,如果您需要恢复原始字节 - 您可以使用编码utf8-c8(即“utf8-clean8”)。

https://docs.raku.org/language/faq.html#String:_How_can_I_get_the_hexadecimal_representation_of_a_string%3F
https://docs.raku.org/language/unicode
https://raku.org

答案4

给出了我最初问题的答案(我会接受其中一个),但为了完整起见,我补充说,python经过一些更改(格式定义更改,副词字形应分组为0x30-0x39

#!/bin/python3
# glycol is the GLYph COLlector
# it collects all used glyphs from the translation json files given as command line arguments
# and prints them as a string formatted to be used as -r argument for lv_font_conv
# Usage: lv_font_conv -r $(glycol de.json en.json fr.json) ...

import sys
Glyphs=[]
# Loop over all files
sys.argv.pop(0)
for file in sys.argv:
    # Sorry, low level tool without error handling
    with open(file, 'r', encoding="utf-8") as f:
        for line in f:
            parts = line.split('"')
            if len(parts) == 5:
                # expect format _"key":"string" -- No json parsing
                Glyphs.extend(ord(c) for c in parts[3])
Glyphs.sort()
# Now loop over the sorted glyph list, skip duplicates, join regions
last=0
region=0
for glyph in Glyphs:
    if (last == 0):
        print(hex(glyph), end='')
    elif (glyph == last + 1):
        region = glyph
    elif (glyph > last):
        if (region == last):
            print('-'+ hex(region), end='')
        print(','+ hex(glyph), end='')
    last = glyph
if (region == last):
    print('-'+ hex(region), end='')
print()

相关内容