提问者:小点点

将US-ASCII强制编码为UTF-8(iconv)


我正在尝试将一堆文件从US-ASCII转换为UTF-8。

为此,我使用图标:

iconv -f US-ASCII -t UTF-8 file.php > file-utf8.php

我的原始文件是 US-ASCII 编码的,这使得转换不会发生。显然,这是因为 ASCII 是 UTF-8 的一个子集......

iconv美国ASCII到UTF-8或ISO-8859-15

并引用:

在引入非ASCII字符之前,不需要以其他方式显示文本文件

没错。如果我在文件中引入一个非ASCII字符并保存它,比方说在Eclipse中,文件编码(字符集)被转换为UTF-8。

在我的例子中,无论如何我都想强制conv将文件转码为UTF-8。不管里面是否有非ASCII字符。

注意:原因是我的PHP代码(非ASCII文件...)正在处理一些非ASCII字符串,这导致字符串无法很好地解释(法语):

Ilé©tait une fois… l'homme sá©rie amié©e mythique d'Albert

Barillã© (Procidis), 1Ã ̈re

...

  • US ASCII - 是 - UTF-8 的一个子集(请参阅下面的 Ned 答案)
  • 这意味着美国ASCII文件实际上是用UTF-8编码
  • 我的问题来自其他地方

共3个答案

匿名用户

ASCII是UTF-8的一个子集,因此所有ASCII文件都已采用UTF-8编码。ASCII文件中的字节和“将其编码为UTF-8”所产生的字节将完全相同。他们之间没有区别,所以没有必要做任何事情。

看起来您的问题是文件实际上不是 ASCII。您需要确定他们使用的编码,并正确转码。

匿名用户

    < li>iconv将使用您指定的任何输入/输出编码,而不管文件的内容是什么。如果指定了错误的输入编码,输出将会乱码。 < li >您可以尝试使用< code>file命令来检测文件的类型/编码。 < li>file仅猜测文件编码,可能是错误的(尤其是在特殊字符仅在大文件中出现较晚的情况下)。 < li >即使在运行< code>iconv后,< code>file也可能不会报告任何更改,因为< code>file尝试猜测编码的方式有限。具体例子见我的长回答。 < li >您可以使用< code>hexdump查看非7位ASCII文本的字节,并与常用编码(UTF-8、ISO 8859-*)的代码表进行比较,以自行决定编码是什么。 < li>7位ASCII(也称为US ASCII)在字节级别上与UTF-8和8位ASCII扩展(ISO 8859-*)相同。因此,如果你的文件只有7位字符,那么你可以称之为UTF-8,ISO 8859-*或美国ASCII,因为在一个字节的水平,他们都是相同的。只有当您的文件包含7位ASCII范围之外的字符时,讨论UTF-8和其他编码才有意义。

我今天碰到了这个,遇到了你的问题。也许我可以补充一些信息来帮助遇到这个问题的其他人。

首先,术语ASCII过载了,这导致了混乱。

7 位 ASCII 仅包含 128 个字符(00-7F 或十进制中的 0-127)。7 位 ASCII 有时也称为 US-ASCII。

ASCII

UTF-8 编码的前 128 个字符使用与 7 位 ASCII 相同的编码。因此,仅包含前 128 个字符范围内的字符的文本文件在字节级别上是相同的,无论是使用 UTF-8 还是 7 位 ASCII 编码。

代码页布局

术语扩展ASCII(或高ASCII)是指八位或更大的字符编码,包括标准的七位ASCII字符,以及其他字符。

扩展ASCII

ISO 8859-1(又名“ISO拉丁1”)是一个特定的8位ASCII扩展标准,涵盖西欧的大多数字符。东欧语言和西里尔语言还有其他ISO标准。ISO

“扩展”是指ISO

ISO 8859和专有改编

在ISO 8位ASCII扩展标准(ISO 8859-*)发布之前,有许多来自IBM,DEC,HP,Apple等的专有8位代码页(将字节映射到字符)。

ISO字符集与代码页的一个显著区别是,字符位置128到159(对应于具有高位集的ASCII控制字符)在ISO标准中特别未使用和未定义,尽管它们通常用于专有代码页中的可打印字符

即,在所有ISO 8位扩展中,不使用字符128-159(80-9F),而在之前的专有代码页中,这些字符用于ASCII控制字符(已存在于7位ASCII中),但具有第8位集。

……进一步混淆事情。

在ISO 8位扩展问世后,微软发布了一个新的代码页windows-1252,它是ISO-8859-1的超集,使用了未使用的ISO字符128-159(80-9F),用于智能引号等。如果您不理解,请比较代码表(iso-8859-1 windows-1252)的第8x行和第9x行。

超集意味着如果您将< code>ISO-8859-1呈现为< code>windows-1252,它看起来很好(因为< code>ISO-8859-1中的所有字符也存在于< code>windows-1252中)...但是,如果您尝试将< code>windows-1252呈现为< code>ISO-8859-1,而呈现的数据恰好包含128-159范围内的字节,那么这些字符将无法正确显示。

用字符集标签ISO-8859-1错误标记Windows-1252文本是很常见的。一个常见的结果是,在非Windows操作系统上,所有的引号和撇号(由文字处理软件中的“智能引号”产生)都被替换为问号或框,使文本难以阅读。大多数现代网络浏览器和电子邮件客户端将媒体类型字符集ISO-8859-1视为Windows-1252以适应这种错误标记。这现在是HTML5规范中的标准行为,该规范要求使用Windows-1252编码对宣传为ISO-8859-1的文档进行解析。

因此,在html5标准中,没有名为ISO-8859-1的编码,而是iso-8859-1是用于编码windows-1252的多个标签之一。

windows-1252 html5编码

我今天学到的一个教训是,我们不能相信< code>file总能正确解释文件的字符编码。

文件(命令)

该命令只告诉文件的外观,而不告诉它是什么(在文件查看内容的情况下)。很容易通过将一个魔术数字放入一个内容与它不匹配的文件来欺骗程序。因此,除了在特定情况下,该命令不能用作安全工具。

文件在文件中查找提示类型的幻数,但这些数字可能是错误的,不能保证正确性file还试图通过查看文件中的字节来猜测字符编码。基本上,<code>file</code>有一系列测试,帮助它猜测文件类型和编码。

我的文件是一个大型CSV文件file将此文件报告为美国ASCII编码,这是错误的。

$ ls -lh
total 850832
-rw-r--r--  1 mattp  staff   415M Mar 14 16:38 source-file
$ file -b --mime-type source-file
text/plain
$ file -b --mime-encoding source-file
us-ascii

我的文件中有变音符号(即 Ö)。第一个非 7 位 ascii 直到超过 100k 行才会显示在文件中。我怀疑这就是为什么文件没有意识到文件编码不是 US-ASCII 的原因。

$ pcregrep -no '[^\x00-\x7F]' source-file | head -n1
102321:�

我在Mac上,所以使用PCRE的< code>grep。对于GNU grep,您可以使用< code>-P选项。或者,在Mac上,你可以安装coreutils(通过自制软件或其他方式)来获得GNU grep。

我还没有深入研究file的源代码,手册页也没有详细讨论文本编码检测,但我猜file在猜测编码之前不会查看整个文件。

无论我的文件编码是什么,这些非 7 位 ASCII 字符都会破坏内容。我的德语 CSV 文件是 ;-分隔并提取单个列不起作用。

$ cut -d";" -f1 source-file > tmp
cut: stdin: Illegal byte sequence
$ wc -l *
 3081673 source-file
  102320 tmp
 3183993 total

请注意剪切错误,我的“tmp”文件只有 102320 行,第 102321 行上的第一个特殊字符。

让我们看看这些非ASCII字符是如何编码的。我将第一个非7位ascii转储到hexdf中,进行一些格式化,删除换行符(0a)并只取前几个。

$ pcregrep -o '[^\x00-\x7F]' source-file | head -n1 | hexdump -v -e '1/1 "%02x\n"'
d6
0a

另一种方式。我知道第一个非7位ASCII字符位于第102321行的第85位。我抓住那一行,告诉hexdump从位置85开始读取两个字节。您可以看到由“.”表示的特殊(非7位ASCII)字符,下一个字节是“M”。。。所以这是一个单字节字符编码。

$ tail -n +102321 source-file | head -n1 | hexdump -C -s85 -n2
00000055  d6 4d                                             |.M|
00000057

在这两种情况下,我们看到特殊字符由< code>d6表示。因为这个字符是一个德国字母,我猜是ISO

重要的问题……如果不确定文件编码,我怎么知道这个字符是一个。答案是上下文。我打开文件,阅读文本,然后确定它应该是什么字符。如果我在Vim中打开它,它会显示为一个。因为Vim比file更好地猜测字符编码(在这种情况下)。

所以,我的文件似乎是ISO

我将跳过检查并转到转换步骤。

$ iconv -f iso-8859-1 -t utf8 source-file > output-file
$ file -b --mime-encoding output-file
us-ascii

嗯,即使在转换之后,file仍然告诉我这个文件是美国ASCII码。让我们再次检查hexdump

$ tail -n +102321 output-file | head -n1 | hexdump -C -s85 -n2
00000055  c3 96                                             |..|
00000057

绝对是改变。注意,我们有两个字节的非7位ASCII码(用“.”表示)右边),两个字节的十六进制代码现在是< code>c3 96。如果我们看一看,似乎我们现在有UTF-8(< code > C3 96 是UTF-8中< code >的编码)UTF-8编码表和Unicode字符

但是< code>file仍然将我们的文件报告为< code>us-ascii?嗯,我认为这又回到了< code>file没有查看整个文件这一点,以及第一个非7位ASCII字符直到文件的末尾才出现这一事实。

我将使用< code>sed在文件的开头添加一个?,看看会发生什么。

$ sed '1s/^/Ö\'$'\n/' source-file > test-file
$ head -n1 test-file
Ö
$ head -n1 test-file | hexdump -C
00000000  c3 96 0a                                          |...|
00000003

酷,我们有一个变音符号。请注意,编码虽然是 c3 96 (UTF-8)。嗯。

再次检查同一文件中的其他元音变音:

$ tail -n +102322 test-file | head -n1 | hexdump -C -s85 -n2
00000055  d6 4d                                             |.M|
00000057

ISO 8859-1。哎呀!这只是显示了搞砸编码是多么容易。为了清楚起见,我已经设法在同一个文件中混合创建了UTF 8和ISO 8859-1编码。

让我们尝试使用前面的变音符号 (Ö) 转换我们损坏的(混合编码)测试文件,看看会发生什么。

$ iconv -f iso-8859-1 -t utf8 test-file > test-file-converted
$ head -n1 test-file-converted | hexdump -C
00000000  c3 83 c2 96 0a                                    |.....|
00000005
$ tail -n +102322 test-file-converted | head -n1 | hexdump -C -s85 -n2
00000055  c3 96                                             |..|
00000057

第一个UTF-8变音符号被解释为ISO

我会再试一次,但这次我将使用 Vim 而不是 sed 进行 Ö 插入。Vim之前似乎更好地检测编码(如“latin1”又名ISO

$ vim source-file
$ head -n1 test-file-2
�
$ head -n1 test-file-2 | hexdump -C
00000000  d6 0d 0a                                          |...|
00000003
$ tail -n +102322 test-file-2 | head -n1 | hexdump -C -s85 -n2
00000055  d6 4d                                             |.M|
00000057

事实上,vim在文件开头插入字符时使用了正确/一致的ISO编码。

现在测试:文件在识别文件开头带有特殊字符的编码方面做得更好吗?

$ file -b --mime-encoding test-file-2
iso-8859-1
$ iconv -f iso-8859-1 -t utf8 test-file-2 > test-file-2-converted
$ file -b --mime-encoding test-file-2-converted
utf-8

是的!故事的寓意。不要相信file总是能猜对您的编码。在同一个文件中混合编码很容易。有疑问时,请查看十六进制。

在处理大文件时解决文件这一特定限制的黑客是缩短文件以确保特殊(非ASCII)字符出现在文件的早期,以便文件更有可能找到它们。

$ first_special=$(pcregrep -o1 -n '()[^\x00-\x7F]' source-file | head -n1 | cut -d":" -f1)
$ tail -n +$first_special source-file > /tmp/source-file-shorter
$ file -b --mime-encoding /tmp/source-file-shorter
iso-8859-1

然后,您可以使用(可能是正确的)检测到的编码作为iconv的输入,以确保正确转换。

Christos Zoulas更新了文件,以使字节数量可配置。有一天,在功能请求上转身,真棒!

http://bugs.gw.com/view.php?id=533允许更改从命令行读取分析文件的字节数

该功能在< code>file版本5.26中发布。

在对编码进行猜测之前,查看更大的文件需要时间。然而,对于更好的猜测可能比额外的时间和I/O更重要的特定用例,有选择是很好的。

使用以下选项:

−P, −−parameter name=value

    Set various parameter limits.

    Name    Default     Explanation
    bytes   1048576     max number of bytes to read from file

大约...

file_to_check="myfile"
bytes_to_scan=$(wc -c < $file_to_check)
file -b --mime-encoding -P bytes=$bytes_to_scan $file_to_check

…如果您想在猜测之前强制<code>file</code>查看整个文件,那么它应该可以做到这一点。当然,只有当您有<code>文件</code>5.26或更高版本时,这才有效。

感谢@theprivileges指出从< code>file 5.44开始参数行为已经改变。现在有一个额外的< code>encoding参数,该参数指定由< code>file读取的字节中有多少字节应该用于编码确定。

例如:

file_to_check="myfile"
bytes_to_scan=$(wc -c < $file_to_check)
file -b --mime-encoding -P bytes=$bytes_to_scan -P encoding=$bytes_to_scan file_to_check="myfile"

注意!通过此更改,用于确定编码的文件的字节现在上限为 64k。因此,对于特殊字符仅出现在文件后期的非常大的文件,您可能需要采用不同的解决方法(例如,在文件中向上移动特殊字符以进行正确检测)。

其他一些答案似乎专注于尝试让file显示UTF-8,即使该文件仅包含纯7位ascii。如果您仔细考虑过,您可能永远不想这样做。

  1. 如果一个文件只包含7位ascii,但file命令表示该文件是UTF-8,这意味着该文件包含一些具有UTF-8特定编码的字符。如果这不是真的,可能会导致混乱或问题。如果file在文件仅包含7位ascii字符时显示UTF-8,则这将是file程序中的错误
  2. 任何需要UTF-8格式输入文件的软件在使用纯7位ascii时都不会有任何问题,因为这在字节级别上与UTF-8相同。如果有软件在接受文件作为输入之前使用file命令输出,并且它不会处理该文件,除非它“看到”UTF-8……那么这是非常糟糕的设计。我认为这是那个程序中的一个错误

如果您绝对必须获取一个普通的7位ascii文件并将其转换为UTF-8,只需在文件中插入一个非7位ascii字符,并对该字符进行UTF-8编码即可。但我无法想象你需要这样做的用例。最容易使用的UTF-8字符是字节顺序标记(BOM),它是一个特殊的非打印字符,提示文件是非ascii字符。这可能是最好的选择,因为它不应该在视觉上影响文件内容,因为它通常会被忽略。

微软编译器和解释器,以及微软Windows上的许多软件,如记事本,将BOM视为必需的幻数,而不是使用启发式方法。这些工具在将文本另存为 UTF-8 时会添加 BOM,并且无法解释 UTF-8,除非存在 BOM 表或文件仅包含 ASCII。

这是关键:

或者文件仅包含 ASCII

因此,windows上的一些工具在读取UTF-8文件时有困难,除非BOM字符存在。然而,这并不影响纯7位ascii文件。也就是说,这不是通过添加BOM字符来强制普通7位ascii文件成为UTF-8的原因。

以下是关于在不需要时使用BOM的潜在陷阱的更多讨论(某些Microsoft应用程序使用的实际UTF-8文件需要它)。https://stackoverflow.com/a/13398447/3616686

尽管如此,如果您仍然想这样做,我很想听听您的用例。这是如何做到的。在UTF-8中,BOM由十六进制序列<code>0xEF、0xBB、0xBF</code>表示,因此我们可以很容易地将此字符添加到纯7位ascii文件的前面。通过向文件中添加非7位ascii字符,文件不再只有7位ascii。请注意,我们根本没有修改或转换原始的7位内容。我们在文件开头添加了一个非7位字符,因此文件不再完全由7位字符组成。

$ printf '\xEF\xBB\xBF' > bom.txt # put a UTF-8 BOM char in new file
$ file bom.txt
bom.txt: UTF-8 Unicode text, with no line terminators
$ file plain-ascii.txt  # our pure 7-bit ascii file
plain-ascii.txt: ASCII text
$ cat bom.txt plain-ascii.txt > plain-ascii-with-utf8-bom.txt # put them together into one new file with the BOM first
$ file plain-ascii-with-utf8-bom.txt
plain-ascii-with-utf8-bom.txt: UTF-8 Unicode (with BOM) text

匿名用户

人们说你做不到,我理解你在提出问题并得到这样的答案时可能会感到沮丧。

如果你真的想让它显示在UTF 8中而不是美国ASCII中,那么你需要分两步来做。

第一:

iconv -f us-ascii -t utf-16 yourfile > youfileinutf16.*

第二:

iconv -f utf-16le -t utf-8 yourfileinutf16 > yourfileinutf8.*

然后,如果您执行文件-i,您将看到新字符集是UTF-8。