在某些情况下,需要知道(使用)每个单独字符的整理顺序。它通常表达为字符类像正则表达式一样[b-d]
。该字符类将仅匹配一个字符在给定的范围内。
哪个个人字符是范围b-d
(或其他范围)内的字符。
还知道 C 语言环境中的整理顺序是每个 ASCII 字符的字节值[A](仅显示 33 到 126 之间的字符):
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
字符范围可以扩展到 ASCII 之外吗?
但:
整理顺序是什么个别字符在其他地区?
有没有办法s̲h̲o̲w̲这样的整理顺序(在任何语言环境中)?
[A]在使用 ASCII 的系统(大多数系统)中,但其他系统可能使用 EBCDIC 甚至其他系统。
答案1
这有几个方面。我们需要列出语言环境字符集中的所有字符,选择图形字符(例如 33 到 126 ASCII 字符)并对它们进行排序。
还有一个问题是谈论字符的排序顺序是否有意义或者它是否曾经被定义过。我将从最后一点开始。
POSIX 校对算法
如果我们谈论的是由、 or 、shell globs 或/ 、字符串比较运算符以及更普遍的大多数基于 POSIX 系统上用户区域设置对文本进行排序的工具实现strcoll()
和使用的C/POSIX 排序算法,请注意他们是为了比较sort
ls
awk
expr
<
>
字符串。
在 GNU 系统上的 en_US.UTF-8 语言环境中,é
由单个é
字符组成的字符串会排序后由单个字符组成的字符串e
,Stéphane
排序前 Stephanie
。在 cs_CZ.UTF-8 语言环境中,在和c
之间排序,但在和 之间排序。b
d
ch
h
i
排序规则算法考虑整个字符串,而不是单独的字符。因此,知道单独比较时的字符顺序并不一定能告诉我们包含这些字符的字符串将如何进行比较。
该算法旨在像现实世界中的许多语言(例如字典和电话簿)一样比较字符串。它有点太简单了,无法涵盖不同文化中排序的所有微妙之处(请参阅重症监护室,它们实现了更多的不同算法),但对于大多数情况来说已经足够好了。
在该算法中,整理元素,其中包括字符,但也包括字符组合,例如韩语字母表或捷克语的多部分字素,ch
或者在某些系统上,表示é
为e
后跟组合锐音符号 (U+0301) 的组合被分配了多个重量。
和整个字符串依次使用每个权重进行比较,从最初的权重到最后的权重。
例如,在该en_US.UTF-8
GNU 语言环境中,E
、é
、e
都É
具有相同的主要权重。Stéphane
并Stephanie
分解为
<S><t><é><p><h><a><n> <e>
<S><t><e><p><h><a><n> <i><e>
整理元素(此处为每个字符一个)。
到 为止n
,两个字符串的整理元素具有相同的主要权重,但i
的主要权重大于e
s,因此Stephanie
在之后排序Stéphane
,甚至不必考虑次要权重。
现在,对于Stephane
vs Stéphane
,当比较主要权重时,它们排序相同,因此必须考虑次要权重。如果您查看/usr/share/i18n/locales/iso14651_t1_common
en_US.UTF-8 语言环境按原样使用的 GNU 系统,您会看到:
<BAS> # 15
[...]
<ACA> # 18
[...]
<U0065> <e>;<BAS>;<MIN>;IGNORE # 259 e
<U00E9> <e>;<ACA>;<MIN>;IGNORE # 260 é
对于拉丁字母表中的字符,辅助权重用于比较变音符号。基本字符 ( BAS
) 排在带有尖音符 ( ) 的字符之前ACA
。所以Stéphane
排序在Stephane
.为了STÉPHANE
与进行比较Stéphane
,我们必须达到第三个权重,其中英语中大写字母排在小写字母之后(例如与爱沙尼亚语相反)。
还有一些非字母数字字符,例如空格或标点符号,其主要权重是IGNORE
且 在第一次比较过程中不被考虑(在和de facto
之间排序,这并不意味着空格在和之间排序)。deface
degree
f
g
对于$'STE\u0301HANE'
vs Stéphane
,某些系统(例如 Solaris)会将其视为E\u0301
具有相同权重的整理元素,除了最后一个字符为É
(U+00C9) 字符,而其他一些系统则将其\u0301
视为标点符号,给出的结果不太好(就像$'STE\u0301HANE'
之前的那样Stephane
)。
不是一个全部的命令
在 GNU 系统上U+0301 的排序顺序甚至没有定义,在这种情况下还有数千个字符。我喜欢以四舍五入的数字(U+2460..U+2473)为例,因为这些数字显然应该有排序顺序,但没有:
$ touch ① ② ③ ④ ⑤
$ ls
④ ③ ⑤ ② ①
$ ls | sort -u
④
还有一些字符实际上被定义为与其他字符具有完全相同的权重(例如 Ǝ
, Ə
,Ɛ
这里的排序都是相同的)。
因此,实际上不可能在某些语言环境中对任意字符进行排序,除非像某些sort
实现那样(并且这将是 POSIX 规范的下一个主要版本的要求),您可以memcmp()
对排序相同的字符进行相似比较。
列出语言环境字符集中的所有图形字符
不同的区域设置可能使用不同的字符集。
字符集主要分为三类:单字节字符集,如 ASCII 或 iso-8859-x,其中每个字节对应一个字符(尽管有些可能未定义);多字节字符集(和编码),如 UTF-8、GB18030 、BIG5 或 EUCJP,其中字符在不同数量的字节上进行编码,以及有状态字节,其中一个字节或字节序列可以代表不同的字符,具体取决于之前是否已发出状态转换代码。
最后一个类别现在很少在语言环境中使用,并且通常难以管理,因此我们现在可以忽略它。
C 语言环境本身保证具有单字节字符集。它不一定是 ASCII,尽管它通常出现在不基于 EBCDIC 的系统上。
请注意,某些脚本(例如英语中使用的拉丁文脚本)是从左到右书写的,而其他一些脚本是从右到左书写的,因此将这些不同脚本的字符(受某些字符集支持)放在同一行不一定是一个好主意。
组合字符也是如此,最终会被组合成随机字符并组合在一起。
另请注意,一些字符集(例如 Unicode)仍在不断发展。虽然现在已固定为 0..0xD7FF、0xE000..0x10FFFF 代码点范围,但其中大部分仍未分配,并且每个新版本的 Unicode 都会分配新的代码点范围,系统供应商会尽力跟上。
graph
ISO/IEC 30112 技术报告 (2014) 列出了分类为的字符,该报告是 ISO/IEC TR 14652 (2002) 的后续版本。 GNU 语言环境似乎遵循这一点,而其他一些语言环境(如 FreeBSD/Solaris)则不然,但我不会责怪它们,因为这对我来说似乎没有多大意义。例如,它排除大多数空格字符,但不排除 U+00A0(不间断空格)、U+2007(数字空格)或 U+200B(零宽度空格)。它包括我认为的角色控制字符,例如 U+200C..U+200F、U+202D、U+202E...² 后一个,从右到左覆盖对于此问答至关重要,因为它颠倒了从左到右字符的顺序:
$ printf '%b\n' '\u202E' a b c | sort | paste -sd '\0' -
abc
(有些浏览器会显示cba
是否支持,其他浏览器会显示abc
)。
它还包括大多数标签人物还有成千上万的私人使用字符,这些字符不太可能被分配,更不用说在您的系统上可绘制了。
对于单字节字符集(在 GNU 系统上,locale ctype-mb-cur-max
返回 1 的字符集),列出图形字符应该只是循环遍历所有 255 个字节值(忽略第一个,每个字符集中的 NUL 不是图形字符集,会导致问题)并将它们与 进行匹配[[:graph:]]
。
awk
例如,人们可以这样做:
awk '
BEGIN{
for (i = 1; i < 256; i++) {
c = sprintf("%c", i)
if (c ~ /[[:graph:]]/) print c
}
}' | sort | paste -sd '\0' -
在使用 iso8859-7 单字节字符集的希腊语语言环境中el_GR.iso88597
,将给出:
`^¨~<=>¦°_-,;:!?/.·'ʽʼ"«»()[]{}§©@$£¤¥*\&#%+±ª―΄΅0½12²3³456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZΑαΆάΒβΓγΔδΕεΈέΖζΗηΉήΘθΙιΊίΪϊΐΚκΛλΜμΝνΞξΟοΌόΠπΡρΣσςΤτΥυΎύΫϋΰΦφΧχΨψΩωΏώ
(尾随不间断空格被 GNU 语言环境错误分类为“图形”)。
该方法不能用于多字节字符。
如果您iconv
支持 aUCS-4BE
或UTF32BE
charset 编码,则可以将所有 unicode 代码点生成为 32 位大端数字并将它们转换为语言环境的字符集:
perl -e 'print pack("L>*", $_, 10) for 1..0xd7ff, 0xe000..0x10ffff' |
iconv -cf UCS-4BE |
grep '[[:graph:]]' |
sort
或者如果它支持 UTF-8:
perl -C -M-warnings=nonchar -le 'print chr$_ for 1..0xd7ff, 0xe000..0x10ffff' |
iconv -cf UTF-8 |
grep '[[:graph:]]' |
sort
(每行保留一个字符以避免上述问题,也避免生成太长的一行)。
这是因为 Unicode(及其编码)旨在包含所有其他可能的字符集中的字符,因此任何字符集中的每个字符始终都有一个 Unicode 代码点。现代系统实际上根据 Unicode 定义其字符集,并且wchar_t
通常对应于 Unicode 代码点。
现在,如上所述,排序采用memcmp()
基于 的比较来对与 排序相同的字符进行比较strcoll()
。对于单字节字符集,这将是对这些字符集中的代码点进行排序;对于 UTF-8,它将按 Unicode 代码点排序,因为 UTF-8 具有该特定属性。对于其他 Unicode 编码(例如中文 GB18030 或其他多字节字符集),这可能或多或少看起来是随机的。
无论如何,这意味着对于具有相同排序顺序的两个语言环境,sort
如果这些语言环境使用不同的字符集,则 的输出将会不同。
例如,如果我们回到①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳四舍五入的数字。 Unicode 按该顺序指定它们(代码点 0x2460 到 0x2473)。在 GNU 语言环境中,它们的顺序没有定义(① 既不在 ② 之前也不在之后)。在使用 UTF-8 的区域设置中,排序会将 ② 放在 ① 之后,因为 UTF-8 顺序遵循 Unicode 代码点顺序。但在像 zh_CN.gb18030 这样使用 GB18030(来自中国的 Unicode 的另一种编码)的语言环境中,顺序变为 ⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳①②③④⑤⑥⑦⑧⑨⑩,具体到这些字符在字节级别的编码方式(或者至少会是,如果不是这个错误这使得它们的顺序为①②③④⑤⑥⑦⑧⑨⑩⑮⑯⑰⑱⑲⑳⑪⑫⑬⑭)。
如果您想根据字符串的排序规则对字符串的字符进行排序,请使用zsh
:
printf "%s\n" ${(j::)${(s::o)string}}
请注意,zsh 的变量中可以包含 NUL 字符,但strcoll()
不适用于这些字符。zsh
试图解决这个问题,但是这并不完美。
如果字符串包含具有相同排序顺序的不同字符,结果将是不确定的。
^2019年编辑① ② ③ ④ ⑤ 的顺序已在较新版本的 GNU libc 中得到修复,但截至 2.30,超过 95% 的字符仍然没有定义的顺序,您可以将 ① ② ③ ④ ⑤ 替换为
答案2
在 bash 和 zsh 中,很容易生成一系列数字:
$ printf '\\x%x' {53..63}
\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f
将 UNICODE 数字转换为字符也很容易:
$ printf '%b' "$(printf '\\U%x' {53..63})"
56789:;<=>?
这是 bash 编码员的礼物,范围为\UXXXXXXXX
没有限制,它的工作范围是从代码 0 到 10FFFF(十进制的 1114111)。
因此,我们可以使用以下简单代码生成所有前 127 个字符:
$ printf '%b' "$(printf '\\U%x' {0..127})" | hd
00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
00000010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f |................|
00000020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f | !"#$%&'()*+,-./|
00000030 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f |0123456789:;<=>?|
00000040 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f |@ABCDEFGHIJKLMNO|
00000050 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f |PQRSTUVWXYZ[\]^_|
00000060 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f |`abcdefghijklmno|
00000070 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f |pqrstuvwxyz{|}~.|
00000080
为每个字符添加一个 NUL (0x00) 字符并使用 sort -z 我们可以看到整理顺序:
$ printf '%b' "$(printf '\\U%x\\0' {0..127})" | sort -z
`^~<=>| _-,;:!?/.'"()[]{}@$*\&#%+
▒▒123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ
并通过删除 NUL,查看数字顺序:
$ printf '%b' "$(printf '\\U%x\\0' {0..127})" | sort -z | tr -d '\0' | hd
00000000 60 5e 7e 3c 3d 3e 7c 20 5f 2d 2c 3b 3a 21 3f 2f |`^~<=>| _-,;:!?/|
00000010 2e 27 22 28 29 5b 5d 7b 7d 40 24 2a 5c 26 23 25 |.'"()[]{}@$*\&#%|
00000020 2b 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |+...............|
00000030 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f |................|
00000040 7f 30 31 32 33 34 35 36 37 38 39 61 41 62 42 63 |.0123456789aAbBc|
00000050 43 64 44 65 45 66 46 67 47 68 48 69 49 6a 4a 6b |CdDeEfFgGhHiIjJk|
00000060 4b 6c 4c 6d 4d 6e 4e 6f 4f 70 50 71 51 72 52 73 |KlLmMnNoOpPqQrRs|
00000070 53 74 54 75 55 76 56 77 57 78 58 79 59 7a 5a |StTuUvVwWxXyYzZ|
0000007f
请注意与 C 语言环境顺序的区别:
$ printf '%b' "$(printf '\\U%x\\0' {0..127})" | LC_COLLATE=C sort -z | tr -d '\0' | hd
00000000 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 |................|
00000010 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000020 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000030 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 |123456789:;<=>?@|
00000040 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 |ABCDEFGHIJKLMNOP|
00000050 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 |QRSTUVWXYZ[\]^_`|
00000060 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 |abcdefghijklmnop|
00000070 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f |qrstuvwxyz{|}~.|
0000007f
一个字节在 ASCII 范围之外可以有 128 个值,打印为 utf8,yield(未按排序规则排序):
$ printf '%b' "$(printf '\\U%x' {125..255})"
}~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ
ÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
按整理顺序排序的相同范围是(注意第一个字符是锐音符和交替的小写和大写元音):
$ printf '%b' "$(printf '\\U%x\\0' {125..255})" | sort -z
´¨~÷׬¦°µ¯¡¿·¸«»}§¶©®¤¢£¥±¼½¾¹²³áÁàÀâÂåÅäÄãêæÆ
çÇðÐéÉèÈêÊëËíÍìÌîÎïÏñÑóÓòÒôÔöÖõÕøغßúÚùÙûÛüÜýÝÿþÞ
当范围扩大时,排序顺序变得更加复杂:
$ printf '%b' "$(printf '\\U%x\\0' {0..500})" | sort -z | tr -d '\0'
´`^¨~÷×<=>¬|¦°µ _¯-,;:!¡?¿/.·¸'"«»()[]{}§¶©®@¤¢$£¥*\&#%+
±ƀƂƃƄƅƉƋƌƍƑƒƔƖƗƚƛƜƝƞƤƥƦƧƨƩƪƫƬƭƮƱƲƸƹƺƻƼƽƾǀǁǂǃ
▒▒0¼½¾1¹2²3³456789aAáÁàÀăĂâÂǎǍåÅäǟÄǞãÃǡǠąĄāĀªæǣÆǢ
bBƁcCćĆĉĈčČċĊçÇƈƇdDďĎđĐƊðÐdzDzDZdžDžDŽeEéÉèÈĕĔêÊěĚëËėĖęĘēĒǝƎƏƐ
fFgGǴğĞĝĜǧǦġĠǥǤģĢƓƣƢhHĥĤħĦƕiIíÍìÌĭĬîÎǐǏïÏĩĨįĮīĪıİijIJ
jJĵĴǰkKǩǨķĶƙƘlLĺĹľĽŀĿłŁļĻljLjLJmMnNńŃňŇñÑņŅʼnŋŊnjNjNJ
oOóÓòÒŏŎôÔǒǑöÖƟőŐõÕøØǫǭǪǬōŌơƠºƆœŒpPqQĸrRŕŔřŘŗŖ
sSśŚŝŜšŠşŞſßtTťŤŧŦţŢuUúÚùÙŭŬûÛǔǓůŮüǘǜǚǖÜǗǛǙǕűŰũŨųŲūŪưƯ
vVwWŵŴƿxXyYýÝŷŶÿŸƴƳzZźŹžŽżŻƶƵþÞƷǯǮ
在纯 C 语言环境中对如此大的字符组进行排序是不可能的,因为 printf 将字符生成为 utf8 中的字节对,并且 sort(使用纯 C 语言环境)将尝试对字节进行排序。否则 printf 将\U…
因 C 语言环境而失败。所需要的是一个等效的语言环境,它允许的字符数多于一个字节可以容纳的 256 个字符:
$ LC_ALL=C.UTF-8 printf "$(printf '\\U%x' {32..500})" | sort
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ
[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·
¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñò
óôõö÷øùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭ
ĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨ
ũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣ
ƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀǁǂǃDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞ
ǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴ
答案3
整理顺序由区域设置文件定义。以下是语言环境 en_US 中所有 ASCII 字符的序列:
codes=$(for i in {32..126}; do echo "u$(printf '%04x' "$i")"; done)
grep_patterns=$(echo "$codes" | sed 's/^/^</; s/$/>/')
codes_locale=$(grep -oif - <<<"$grep_patterns" /usr/share/i18n/locales/iso14651_t1_common | tr -d '<>')
# "ASCII sequence"
for c in $codes; do echo -ne "\\$c"; done; echo
# "en_US sequence"
for c in $codes_locale; do echo -ne "\\$c"; done; echo
# OUTPUT:
# !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
# !"#%&'()*+,-./:;<=>?@[\]^_`{|}~$0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
应该记住,序列顺序与排序顺序不同。排序期间适用附加规则。