![文件中出现奇怪的字符](https://linux22.com/image/146666/%E6%96%87%E4%BB%B6%E4%B8%AD%E5%87%BA%E7%8E%B0%E5%A5%87%E6%80%AA%E7%9A%84%E5%AD%97%E7%AC%A6.png)
我有一个 UTF-8 文件,其中包含一个奇怪的字符——对我来说就像这样
<96>
这就是它出现在vi
以及它如何出现在gedit
以及它在 LibreOffice 下的显示方式
这使得一系列基本的 Unix 工具出现异常,包括:
cat file
让角色消失,more
并且- 我无法在 vi/vim 中复制和粘贴——它甚至找不到自己
grep
也无法显示任何内容,就好像该角色不存在一样。
该程序file
工作正常并识别它是一个 UTF-8 文件。我还知道,由于该文件的性质,它很可能来自网络上的复制和粘贴,并且该字符最初代表 EMDASH。
我的基本问题是:
- 这个文件有什么问题吗?
- 如何在同一文件中搜索它的其他出现情况?
- 如何 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"