文件中出现奇怪的字符

文件中出现奇怪的字符

我有一个 UTF-8 文件,其中包含一个奇怪的字符——对我来说就像这样

<96>

这就是它出现在vi

vi 截图

以及它如何出现在gedit

gedit 截图

以及它在 LibreOffice 下的显示方式

LibreOffice 截图

这使得一系列基本的 Unix 工具出现异常,包括:

  1. cat file让角色消失,more并且
  2. 我无法在 vi/vim 中复制和粘贴——它甚至找不到自己
  3. grep也无法显示任何内容,就好像该角色不存在一样。

该程序file工作正常并识别它是一个 UTF-8 文件。我还知道,由于该文件的性质,它很可能来自网络上的复制和粘贴,并且该字符最初代表 EMDASH。

我的基本问题是:

  1. 这个文件有什么问题吗?
  2. 如何在同一文件中搜索它的其他出现情况?
  3. 如何 grep 查找可能包含相同问题/字符的其他文件?

该文件可以在这里找到:文件.txt

答案1

该文件包含字节C2 96,它们是UTF-8代码点 U+0096 的编码。该代码点是其中之一C1控制字符通常称为 SPA“保护区起点”(或“受保护区域”)。这对于任何现代系统来说都不是一个有用的字符,但它不太可能是有害它就在那里。

其原始来源可能是某些单字节 8 位编码中的字节 0x96,该编码在途中的某个地方被错误地转码。大概这原本是一个Windows CP1252破折号“–”,在该编码中具有字节值 96 - 大多数其他可能的候选者在位置 80-9F 处设置了控制 - 它已被转换为 UTF-8,就好像它是 latin-1 (ISO/IEC 8859-1),这种情况并不少见。这将导致字节被解释为控制字符并进行相应的翻译,如您所见。


您可以使用该iconv工具修复此文件,该工具是 glibc 的一部分。

iconv -f utf-8 -t iso-8859-1 < mwe.txt | iconv -f cp1252 -t utf-8

为我生成您的最小示例的正确版本。首先将 UTF-8 转换为 latin-1(反转之前的误译),然后重新解释作为 cp1252 将其正确转换回 UTF-8。

然而,这确实取决于真实文件中的其他内容。如果其他地方有 Latin-1 之外的字符,它将失败,因为它无法在第一步正确编码这些字符。

如果你没有 iconv,或者它对真实文件不起作用,你可以直接使用 sed 替换字节:

LC_ALL=C sed -e $'s/\xc2\x96/\xe2\x80\x93/g' < mwe.txt

这将替换C2 96为 UTF-8 破折号编码E2 80 93。您还可以将其替换为例如一个或两个连字符,方法是将其更改\xe2\x80\x93--


您可以以类似的方式 grep。我们用来LC_ALL=C确保我们正在读取实际的字节,而不是grep解释事物:

LC_ALL=C grep -R $'\xc2\x96` .

将列出该目录下所有出现的字节。如果您有混合内容,您可能希望将其限制为仅文本文件,因为二进制文件经常包含任何字节对。

答案2

0x96 是 Windows 代码页 1252 中的一个破折号。c2它前面的字节似乎是双角字符中的默认第一个字节。其他人可以更准确地解释它。

要搜索其他出现的情况,请在命令模式下将光标放在其上,点击yl(猛拉一个字符),然后键入/<Ctrl>+r"。 (ctrl+r 允许您将寄存器的内容插入到命令中,寄存器"是上次被拉出的内容)。

如果您希望它在终端中呈现,只需将其替换为两个连字符即可。如果这是您拥有的 bibtex 文件,那么两个连字符是键入它的适当方法。

为了展示如何找到该字符的出现,您可以通过十六进制转储工具(例如xxd.

$ cat tmp | xxd | grep c296
00000000: 7061 6765 733d 7b31 c296 3935 7d2c 0a70  pages={1..95},.p
00000020: 6765 733d 7b31 c296 3935 7d2c 0a70 6167  ges={1..95},.pag
00000040: 733d 7b31 c296 3935 7d2c 0a70 6167 6573  s={1..95},.pages
00000060: 7b31 c296 3935 7d2c 0a70 6167 6573 3d7b  {1..95},.pages={
00000080: c296 3935 7d2c 0a70 6167 6573 3d7b 31c2  ..95},.pages={1.
00000090: 9639 357d 2c0a 7061 6765 733d 7b31 c296  .95},.pages={1..
000000b0: 357d 2c0a 7061 6765 733d 7b31 c296 3935  5},.pages={1..95
000000d0: 2c0a 7061 6765 733d 7b31 c296 3935 7d2c  ,.pages={1..95},
000000f0: 7061 6765 733d 7b31 c296 3935 7d2c 0a70  pages={1..95},.p
00000110: 6765 733d 7b31 c296 3935 7d2c 0a70 6167  ges={1..95},.pag
00000130: 733d 7b31 c296 3935 7d2c 0a70 6167 6573  s={1..95},.pages
00000150: 7b31 c296 3935 7d2c 0a70 6167 6573 3d7b  {1..95},.pages={

答案3

文件中的文本是pages = {1113},,是的,它看起来像数字1113,但实际上第一个字符后面有一个不同的字符1。是的,您可以从该网页的编辑链接中复制粘贴字符串以获取编码字符。

我们可以使用一些工具查看字符串内部:

$ a='pages = {1113},'

或者,为了使其明确清晰并允许轻松复制粘贴,而无需使用编辑页面:

$ a=$(printf 'pages = {1\xc2\x96113},') 

$ echo "$a" | od -An -tx1c
  70  61  67  65  73  20  3d  20  7b  31  c2  96  31  31  33  7d
   p   a   g   e   s       =       {   1 302 226   1   1   3   }
  2c  0a
   ,  \n

$ echo "$a" | sed -n l
pages = {1\302\226113},$

$ echo "$a" | xxd
00000000: 7061 6765 7320 3d20 7b31 c296 3131 337d  pages = {1..113}
00000010: 2c0a

因此,字符是两个字节值c2 96(十六进制)或302 226(八进制)。
它可能是 的字节值的 UTF-8 编码96,或表示为 Unicode 字符:U-0096

该值(在当前的 UTF-8 中,或者更好的是在 ISO-8859-1 中)是控制字符 C1 区域中的控制字符(维基百科页面) 和 (统一码 PDF),十进制数从 128 到 159。具体来说,U-0096 被称为“防护区域开始”或温泉

该值 (dec 150) 超出了 ASCII 范围 (0-127),并且(在较早的时期)用于表示多个字符,具体取决于所使用的代码页。似乎可以合理地假设它以前是 Windows-1252 中编码的破折号(用于标记范围 1-113)(微软页面) (维基百科 1252)并称为破折号(这是两个破折号中较小的一个zh) (维基百科和破折号) 或者通俗地说,就是破折号 ( -)。


Q1:这个文件有什么问题吗?

事实并非如此,控制字符是有效字符,很少使用,但仍然有效。

但您可以用破折号替换它们以使编辑更容易。

<file.txt sed 's/\xc2\x96/-/'

Q2 - 如何在同一文件中搜索它的其他出现情况?

sed -n '/\xc2\x96/p'             # will print lines that contain that character.

或者,grep 可以搜索该字符(由于该字符不可打印,因此颜色突出显示将不可见)并打印该行。

c="$(printf "\U96")" ; grep "$c" file.txt

或者更广泛地说,找到该控制字符范围内的所有字符并列出包含这些字符的文件:

grep -rlP "[\x80-\x9f]"    

Q3 - 如何 grep 查找可能包含相同问题/字符的其他文件?

这将列出 ( -l) 与该字符匹配的文件。

grep -rlP "\x96"

相关内容