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

Posted

技术标签:

【中文标题】强制从 US-ASCII 编码为 UTF-8 (iconv)【英文标题】:Force encode from US-ASCII to UTF-8 (iconv) 【发布时间】:2012-07-03 10:36:13 【问题描述】:

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

为此,我使用的是 iconv:

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

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

iconv US ASCII to UTF-8 or ISO-8859-15

并引用:

在非 ASCII 之前,文本文件不需要以其他方式出现 人物介绍

没错。如果我在文件中引入一个非 ASCII 字符并保存,假设使用Eclipse,文件编码(字符集)将切换为 UTF-8。

就我而言,我想强制 iconv 将文件转码为 UTF-8。里面是否有非ASCII字符。

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

Il était une fois... l'homme série animée mythique d'Albert

Barilla© (Procidis), 1ère

...

US ASCII -- -- UTF-8 的子集(参见下面的Ned's answer) 表示美国 ASCII 文件实际上编码为UTF-8 我的问题来自其他地方

【问题讨论】:

你还记得你的问题是从哪里来的吗?我也有类似的问题 @DrogoNevets 记不太清了,但我认为这与在 PHP 中使用 UTF8 以及与数据库之间的操作有关...utf8_encodeutf8_decode 等...或者更深入:toptal.com/php/a-utf-8-primer-for-php-and-mysql***.com/questions/279170/utf-8-all-the-way-through 反之(utf8 到 ASCII),见How to remove accents and turn letters into “plain” ASCII characters?。 【参考方案1】:

简答

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

长答案

我今天遇到了这个问题,并遇到了您的问题。也许我可以添加更多信息来帮助遇到此问题的其他人。

ASCII

首先,术语 ASCII 被重载,这会导致混淆。

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

ASCII

UTF-8

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

Codepage layout

ISO 8859-* 和其他 ASCII 扩展

extended ASCII(或 high ASCII)一词是指 8 位或更大的字符编码,包括标准的 7 位 ASCII 字符以及附加字符。

Extended ASCII

ISO 8859-1(又名“ISO Latin 1”)是一种特定的 8 位 ASCII 扩展标准,涵盖了西欧的大多数字符。东欧语言和西里尔语言还有其他 ISO 标准。 ISO 8859-1 包括对德语和西班牙语的 Ö、é、ñ 和 ß 等字符的编码(UTF-8 也支持这些字符,但底层编码不同)。

“扩展”是指 ISO 8859-1 包含 7 位 ASCII 标准并使用第 8 位向其添加字符。因此,对于前 128 个字符,ISO 8859-1 在字节级别上等同于 ASCII 和 UTF-8 编码文件。但是,当您开始处理前 128 个字符以外的字符时,您在字节级别不再是 UTF-8 等价物,如果您希望“扩展 ASCII”编码文件为 UTF-8 编码,则必须进行转换。

ISO 8859 and proprietary adaptations

使用file 检测编码

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

file (command)

该命令只告诉文件看起来像什么,而不是它是什么(在文件查看内容的情况下)。通过将幻数放入内容不匹配的文件中很容易欺骗程序。因此,该命令只能在特定情况下用作安全工具。

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

我的文件是一个大的 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 行才会显示。我怀疑这就是file 没有意识到文件编码不是 US-ASCII 的原因。

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

我在 Mac 上,所以使用 PCRE's grep。使用 GNU grep 您可以使用 -P 选项。或者,在 Mac 上,可以安装 coreutils(通过 Homebrew 或其他)以获得 GNU grep。

我没有深入研究file的源代码,man page也没有详细讨论文本编码检测,但我猜file在猜测之前没有看整个文件编码。

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

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

注意cut 错误,我的“tmp”文件只有 102320 行,第一个特殊字符位于第 102321 行。

让我们看看这些非ASCII字符是如何编码的。我将第一个非 7 位 ascii 转储到 hexdump,进行一些格式化,删除换行符 (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

在这两种情况下,我们都看到特殊字符由d6 表示。由于这个字符是一个 Ö,它是一个德语字母,我猜 ISO 8859-1 应该包括这个。果然,你可以看到“d6”是匹配的(ISO/IEC 8859-1)。

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

所以,我的文件似乎是 ISO 8859-1。从理论上讲,我应该检查其余的非 7 位 ASCII 字符,以确保 ISO 8859-1 非常适合......在将文件写入磁盘时,没有什么会迫使程序只使用单一编码(除了礼貌)。

我将跳过检查并继续进行转换步骤。

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

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

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

绝对是改变。请注意,我们有两个非 7 位 ASCII 字节(由右侧的“.”表示),这两个字节的十六进制代码现在是 c3 96。如果我们看一下,似乎我们现在有 UTF-8(c3 96Ö 在 UTF-8 中的编码)UTF-8 encoding table and Unicode characters

file 仍将我们的文件报告为us-ascii?好吧,我认为这可以追溯到 file 不查看整个文件以及第一个非 7 位 ASCII 字符直到文件后期才出现这一事实。

我将使用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 8859-1,因为这是我们告诉 iconv...不是我们想要的,但这就是我们告诉 iconf 要做的。第二个变音符号正确地从 d6 (ISO 8859-1) 转换为 c3 96 (UTF-8)。

我会再试一次,但这次我将使用 Vim 进行 Ö 插入,而不是 sed。 Vim 之前似乎可以更好地检测编码(如“latin1”又名 ISO 8859-1),因此它可能会插入具有一致编码的新 Ö。

$ 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 总是猜对你的编码。在同一个文件中混合编码很容易。如有疑问,请查看十六进制。

在处理大文件时解决file 的这一特定限制的一个技巧是缩短文件以确保特殊(非ascii)字符出现在文件的早期,因此file 更有可能找到他们。

$ 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 更新了file 以使查看的字节数可配置。功能请求的一天周转,太棒了!

http://bugs.gw.com/view.php?id=533 Allow altering how many bytes to read from analyzed files from the command line

该功能在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

...如果您想强制file 在猜测之前查看整个文件,它应该可以解决问题。当然,这只有在您拥有file 5.26 或更高版本时才有效。

强制file 显示 UTF-8 而不是 US-ASCII

其他一些答案似乎集中在尝试使 file 显示 UTF-8,即使该文件仅包含纯 7 位 ascii。如果你深思熟虑,你可能永远不想这样做。

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

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

Microsoft 编译器和解释器,以及许多软件 记事本等 Microsoft Windows 将 BOM 视为必需的魔法 数字而不是使用启发式方法。这些工具在保存时添加 BOM 文本为 UTF-8,并且 除非 BOM 存在,否则无法解释 UTF-8 或者文件只包含 ASCII

这是关键:

或文件只包含 ASCII

因此,Windows 上的某些工具无法读取 UTF-8 文件,除非 BOM 字符存在。但是,这不会影响纯 7 位纯 ascii 文件。 IE。这不是通过添加 BOM 字符来强制普通 7 位 ascii 文件为 UTF-8 的原因。

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

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

$ 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

【讨论】:

确实,file 只查看文件的前几 kb 即可得出结论。 感谢您的反馈,我更新了我的答案以提供更多帮助。 ;) 我添加了缺失的链接,但我不确定最后一个是否猜对了。 (我也想修复useless cat,但我会留给你自己。) 很好的解释。这应该是最佳答案。我有你在这里描述的确切场景。【参考方案2】:

仅供参考,file 默认情况下不会检查整个内容(正如mattpr 的长答案中已经提到的那样)来检测文件的编码。要强制扫描整个内容以进行字符集检测,可以使用此代码...

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

另见相应手册https://man7.org/linux/man-pages/man1/file.1.html

【讨论】:

【参考方案3】:
vim -es '+set fileencoding=utf-8' '+wq!' file

-esexscript 模式下运行vim,因此不会渲染任何内容。然后它执行设置文件编码的命令(vim负责细节),然后关闭文件'+wq!'。

我迟到了这个问题,但是之前使用 iconv 的答案根本没有完成这项工作,即使添加 -c 以删除这些字符,文件也处于非 utf-8 字符的状态。

【讨论】:

【参考方案4】:

我不小心用 UTF-7 编码了一个文件并且遇到了类似的问题。当我输入 file -i name.file 时,我会得到charset=us-ascii

iconv -f us-ascii -t utf-9//translit name.file 不起作用,因为我收集到 UTF-7 是 US ASCII 的子集,UTF-8 也是。

为了解决这个问题,我输入了

iconv -f UTF-7 -t UTF-8//TRANSLIT name.file -o output.file

除了其他人在这里建议的编码之外,我不确定如何确定编码。

【讨论】:

【参考方案5】:

受到Mathieu's answer 和Marcelo's answer 的启发:

我需要看到 file -i myfile.htm 以显示 UTF-8 而不是 US ASCII(是的,我知道它是 UTF-8 的子集)。

因此,这里有一个受先前答案启发的衬线,它将在 Linux 上将所有 *.htm 文件从美国 ASCII 转换为 UTF-8,因此file -i 将向您显示 UTF-8。您可以更改 *.htm(以下命令中的两个位置)以满足您的需要。

mkdir backup 2>/dev/null; for f in $(file -i *.htm | grep -i us-ascii | cut -d ':' -f 1); do iconv -f "us-ascii" -t "utf-16" $f > $f.tmp; iconv -f "utf-16le" -t "utf-8" $f.tmp > $f.utf8; cp $fic backup/; mv $f.utf8 $f; rm $f.tmp; done; file -i *.htm

【讨论】:

【参考方案6】:

以下转换一个文件夹中的所有文件。

创建原始文件的备份文件夹

mkdir backup

将所有美国 ASCII 编码的文件转换为 UTF-8(单行命令)

for f in $(file -i * .sql | grep us-ascii | cut -d ':' -f 1); do iconv -f us-ascii -t utf-8 $f -o $ f.utf-8 && mv $f backup / && mv "$f.utf-8" $f; done

将所有以 ISO 8859-1 编码的文件转换为 UTF-8(单行命令)

for f $(file -i * .sql | grep iso-8859-1 | cut -d ':' -f 1); do iconv -f iso-8859-1 -t utf-8 $f -o $f.utf-8 && mv $f backup / && mv "$f.utf-8" $f; done

【讨论】:

【参考方案7】:

这是一个脚本,它将查找与您传递的模式匹配的所有文件,然后将它们从当前文件编码转换为 UTF-8。如果编码是 US ASCII,那么它仍然会显示为 US ASCII,因为那是 UTF-8 的子集。

#!/usr/bin/env bash
find . -name "$1" |
    while read line;
    do
        echo "***************************"
        echo "Converting $line"

        encoding=$(file -b --mime-encoding $line)
        echo "Found Encoding: $encoding"

        iconv -f "$encoding" -t "utf-8" $line -o $line.tmp
        mv $line.tmp $line
    done

【讨论】:

【参考方案8】:

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

如果您真的希望它以 UTF-8 而不是 US ASCII 显示,那么您需要分两步完成。

第一:

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

第二:

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

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

【讨论】:

谢谢,这正是我所需要的【参考方案9】:

US ASCII 和 UTF-8 没有区别,所以不需要重新转换。

但这里有一点提示,如果您在重新编码时遇到特殊字符问题。

在 source-charset-Parameter 之后添加 //TRANSLIT。

示例:

iconv -f ISO-8859-1//TRANSLIT -t UTF-8 filename.sql > utf8-filename.sql

这有助于我处理奇怪类型的引号,它们总是会破坏字符集重新编码过程。

【讨论】:

【参考方案10】:

我认为Ned's got the core of the problem -- 你的文件实际上不是 ASCII。试试

iconv -f ISO-8859-1 -t UTF-8 file.php > file-utf8.php

我只是猜测您实际上是在使用ISO 8859-1。它在大多数欧洲语言中都很流行。

【讨论】:

不。它没有成功.. 我试过了,但无论如何,如果我运行$ file --mime file.php,我会得到file.php: text/x-php charset=us-ascii...所以我认为我的文件实际上是 ASCII 编码的? file 不会检查整个文件;尝试将字符串移动到文件的顶部,可能在注释块中。 查看是否有 ascii 文件的另一种方法是运行类似以下 Ruby 程序的脚本:File.open("file.php").each_char |c| puts c if c.ord &gt; 127。 (我之所以选择 Ruby,是因为我知道如何快速编写此代码;任何其他类似的语言都会同样简单。) 根据 Smultron 我的文件是 Unicode (UTF-8) 编码的......所以 Ned 确实是正确的。 US-ASCII 是 UTF-8 的子集。那么我的问题应该来自其他问题(问题是我没有处理 php 文件中的非 ASCII 字符串,而是通过互联网接收它们:我正在抓取网页......)。感谢您的宝贵时间!【参考方案11】:

您可以使用file -i file_name 来检查您的原始文件格式到底是什么。

一旦你得到它,你可以执行以下操作:

iconv -f old_format -t utf-8 input_file -o output_file

【讨论】:

【参考方案12】:

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

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

【讨论】:

以上是关于强制从 US-ASCII 编码为 UTF-8 (iconv)的主要内容,如果未能解决你的问题,请参考以下文章

从任何编码强制字符串为 UTF-8

如何在 Java 中将 UTF-8 转换为 US-Ascii

PayPal Payflow 网关 UTF-8 字符

如何在 Linux 中将文件编码转换为 UTF-8

django rest_framework中将json输出字符强制为utf-8编码

强制 XDocument 使用 UTF-8 编码写入字符串