使用字符范围 grep 查找俄语字符

too*_*mas 6 regex unicode grep

如何使用字符范围从文本文件中 grep 查找包含 '\xd0\x99' 和 '\xd0\xb9' 的行?

\n

在 Unicode 中,俄语大写字符(除了 '\xd0\x81')按字母顺序排列在 0x410 到 0x42f 范围内,小字符(除了 '\xd1\x91')按字母顺序排列在 0x430 到 0x44f 范围内。这意味着[\xd0\x90-\xd0\x98\xd0\x9a-\xd0\xaf\xd0\x81]应该匹配除 '\xd0\x99' 之外的所有俄语字符,并且 [\xd0\xb0-\xd0\xb8\xd0\xba-\xd1\x8f\xd1\x91] 应匹配除 '\xd0 之外的所有俄语字符\xb9'。但事实证明情况并非如此。

\n

为了进行实验,我创建了一个输出俄语字符 [\xd0\x96-\xd0\x9c\xd0\xb6-\xd0\xbc] 的函数,每行一个:

\n
rus () { for char in \xd0\x96 \xd0\x97 \xd0\x98 \xd0\x99 \xd0\x9a \xd0\x9b \xd0\x9c \xd0\xb6 \xd0\xb7 \xd0\xb8 \xd0\xb9 \xd0\xba \xd0\xbb \xd0\xbc; do echo $char; done; }\n
Run Code Online (Sandbox Code Playgroud)\n

我还导出了适当的整理设置:

\n
export LC_COLLATE=ru_RU.UTF-8\n
Run Code Online (Sandbox Code Playgroud)\n

如果没有字符范围,一切都会按预期进行:

\n
rus | grep -v "[\xd0\x90\xd0\x91\xd0\x92\xd0\x93\xd0\x94\xd0\x95\xd0\x81\xd0\x96\xd0\x97\xd0\x98\xd0\x9a\xd0\x9b\xd0\x9c\xd0\x9d\xd0\x9e\xd0\x9f\xd0\xa0\xd0\xa1\xd0\xa2\xd0\xa3\xd0\xa4\xd0\xa5\xd0\xa6\xd0\xa7\xd0\xa8\xd0\xa9\xd0\xaa\xd0\xab\xd0\xac\xd0\xad\xd0\xae\xd0\xaf\xd0\xb0\xd0\xb1\xd0\xb2\xd0\xb3\xd0\xb4\xd0\xb5\xd1\x91\xd0\xb6\xd0\xb7\xd0\xb8\xd0\xb9\xd0\xba\xd0\xbb\xd0\xbc\xd0\xbd\xd0\xbe\xd0\xbf\xd1\x80\xd1\x81\xd1\x82\xd1\x83\xd1\x84\xd1\x85\xd1\x86\xd1\x87\xd1\x88\xd1\x89\xd1\x8a\xd1\x8b\xd1\x8c\xd1\x8d\xd1\x8e\xd1\x8f]"\n
Run Code Online (Sandbox Code Playgroud)\n

\n
rus | grep -v "[\xd0\x90\xd0\x91\xd0\x92\xd0\x93\xd0\x94\xd0\x95\xd0\x81\xd0\x96\xd0\x97\xd0\x98\xd0\x99\xd0\x9a\xd0\x9b\xd0\x9c\xd0\x9d\xd0\x9e\xd0\x9f\xd0\xa0\xd0\xa1\xd0\xa2\xd0\xa3\xd0\xa4\xd0\xa5\xd0\xa6\xd0\xa7\xd0\xa8\xd0\xa9\xd0\xaa\xd0\xab\xd0\xac\xd0\xad\xd0\xae\xd0\xaf\xd0\xb0\xd0\xb1\xd0\xb2\xd0\xb3\xd0\xb4\xd0\xb5\xd1\x91\xd0\xb6\xd0\xb7\xd0\xb8\xd0\xba\xd0\xbb\xd0\xbc\xd0\xbd\xd0\xbe\xd0\xbf\xd1\x80\xd1\x81\xd1\x82\xd1\x83\xd1\x84\xd1\x85\xd1\x86\xd1\x87\xd1\x88\xd1\x89\xd1\x8a\xd1\x8b\xd1\x8c\xd1\x8d\xd1\x8e\xd1\x8f]"\n
Run Code Online (Sandbox Code Playgroud)\n

分别输出'\xd0\x99'和'\xd0\xb9'。

\n

对于字符范围,[\xd0\x90-\xd0\x98\xd0\x9a-\xd0\xaf\xd0\x81\xd0\xb0-\xd0\xb8\xd0\xba-\xd1\x8f\xd1\x91]应匹配除 '\xd0\x99' 和 '\xd0\xb9' 之外的所有俄语字符,结果证明这是正确的。但是当我只想过滤 '\xd0\x99' 或仅过滤 '\xd0\xb9' 时,发生了一些有趣的事情:在我的系统上,两者

\n
rus | grep -v "[\xd0\x90-\xd0\xaf\xd0\x81\xd0\xb0-\xd0\xb8\xd0\xba-\xd1\x8f\xd1\x91]"  # expected output: '\xd0\xb9'\n
Run Code Online (Sandbox Code Playgroud)\n

\n
rus | grep -v "[\xd0\x90-\xd0\x98\xd0\x9a-\xd0\xaf\xd0\x81\xd0\xb0-\xd1\x8f\xd1\x91]"  # expected output '\xd0\x99'\n
Run Code Online (Sandbox Code Playgroud)\n

什么也不输出!

\n

'\xd0\x99' 和 '\xd0\xb9' 在这方面并不特殊;使用字母“\xd0\x9f”和“\xd0\xbf”进行类比实验显示出相同的效果。

\n

grep 是否可能出于某种原因在字符范围内默认不区分大小写地处理俄语或西里尔字符?不,不是:添加--no-ignore-case到所有这些 grep 命令中没有任何改变。

\n

这是怎么回事?我在 grep 中发现了错误吗?或者我错过了什么?

\n

(我使用的是 GNU grep 3.11(用 pcre 构建)和 bash 5.1.16。)

\n

Mar*_*eed 3

首先,您应该引用 grep 的参数;如果不这样做,并且当前目录中有一个文件,其名称是单个俄语字母,则该字母将是唯一传递给grep.

\n

但问题是,不带 PCRE 的 grep 似乎按字节工作,无论区域设置如何。所以我认为你需要打开 Perl 兼容模式-P

\n
$ rus | grep -Pv '[\xd0\x90-\xd0\xaf\xd0\x81\xd0\xb0-\xd0\xb8\xd0\xba-\xd1\x8f\xd1\x91]'\n\xd0\xb9\n
Run Code Online (Sandbox Code Playgroud)\n

每当您怀疑将参数解释为 时出现问题grep,一个好的健全性检查就是回退到发送纯 ASCII 字符串,使用\\x{...}非 ASCII 字符的语法(这也是 PCRE 的一个功能,因此仅适用于-P)来发送纯 ASCII 字符串:

\n
$ rus | grep -Pv '[\\x{0410}-\\x{042f}\xd0\x81x\\{0430}-\\x{0438}\\x{043a}-\\x{044f}\xd1\x91]'\n\xd0\xb9\n
Run Code Online (Sandbox Code Playgroud)\n