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

问题描述 投票:52回答:8

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

为此,我正在使用iconv:

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

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

http://www.linuxquestions.org/questions/linux-software-2/iconv-us-ascii-to-utf-8-or-iso-8859-15-a-705054/

引用:

在引入非ascii字符之前,不需要显示文本文件

真正。如果我在文件中引入非ASCII字符并保存它,那么假设使用Eclipse,文件编码(charset)将切换为UTF-8。

在我的情况下,我想强制iconv将文件转码为UTF-8。是否存在非ASCII字符。

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

曾几何时......阿尔伯特的传奇动画系列人物

Barillé(Procidis),1?

...

编辑

  • US-ASCII - 是 - UTF-8的一个子集(见下面的Ned's answer
  • 这意味着US-ASCII文件实际上是在UTF-8中编码的
  • 我的问题来自其他地方
utf-8 character-encoding iconv
8个回答
59
投票

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

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


36
投票

Short Answer

  • file只猜测文件编码,可能是错误的(特别是在特殊字符只出现在大文件后期的情况下)。
  • 您可以使用hexdump查看非7位ascii文本的字节,并与常见编码的代码表(iso-8859- *,utf-8)进行比较,以自行决定编码是什么。
  • iconv将使用您指定的任何输入/输出编码,无论文件的内容是什么。如果指定错误的输入编码,则输出将出现乱码。
  • 即使在运行iconv之后,由于file试图猜测编码的方式有限,file可能不会报告任何变化。有关具体示例,请参阅我的答案。
  • 7位ascii(aka us-ascii)在字节级别与utf-8和8位ascii扩展(iso-8859- *)相同。因此,如果您的文件只有7位字符,那么您可以将其命名为utf-8,iso-8859- *或us-ascii,因为在字节级别它们都是相同的。只有当您的文件具有7位ascii范围之外的字符时,才能谈论utf-8和其他编码(在此上下文中)。

Long Answer

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

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

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

https://en.wikipedia.org/wiki/ASCII

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

https://en.wikipedia.org/wiki/UTF-8#Codepage_layout

术语扩展ascii(或高ascii)是指包含标准七位ASCII字符和其他字符的八位或更大字符编码。

https://en.wikipedia.org/wiki/Extended_ASCII

ISO-8859-1(又名“ISO Latin 1”)是一种特定的8位ASCII扩展标准,涵盖了西欧的大多数字符。东欧语言和西里尔语有其他ISO标准。 ISO-8859-1包括德语和西班牙语的Ö,é,ñ和ß字符。 “扩展”表示ISO-8859-1包含7位ASCII标准,并使用第8位为其添加字符。因此对于前128个字符,它在字节级别上等同于ASCII和UTF-8编码的文件。但是,当您开始处理超过前128个字符时,您在字节级别不再是UTF-8等效项,如果您希望“扩展ascii”文件是UTF-8编码,则必须进行转换。

https://en.wikipedia.org/wiki/Extended_ASCII#ISO_8859_and_proprietary_adaptations

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

https://en.wikipedia.org/wiki/File_%28command%29

该命令仅告知文件的外观,而不是它的内容(在文件查看内容的情况下)。通过将一个幻数放入一个内容与之不匹配的文件中,很容易欺骗程序。因此,除特定情况外,该命令不能用作安全工具。

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

我的文件是一个大型CSV文件。 file将此文件报告为us-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的grep。使用gnu grep,您可以使用-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

注意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”是一场比赛(https://en.wikipedia.org/wiki/ISO/IEC_8859-1#Codepage_layout)。

重要的问题...我怎么知道这个字符是Ö而不确定文件编码?答案是背景。我打开文件,阅读文本,然后确定它应该是什么字符。如果我在vim中打开它,它显示为Ö因为vimfile更好地猜测字符编码(在这种情况下)。

所以,我的文件似乎是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中Ö的正确编码)http://www.utf8-chartable.de/

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。哎呀!只是表明将编码搞砸是多么容易。

让我们尝试使用前面的变音符号转换我们的新测试文件,看看会发生什么。

$ 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的。第二个变音符号正确地从d6转换为c3 96

我会再试一次,这次我会用vim做Ö插入而不是sedvim似乎更好地检测编码(如“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

看起来不错。对于新旧变音符号看起来像ISO-8859-1。

现在测试。

$ 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特定限制的hack(也容易失败)将缩短文件以确保特殊字符出现在文件的早期,因此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

Update

Christos Zoulas更新了file,使得字节数可以配置。有一天功能请求的转变,太棒了!

http://bugs.gw.com/view.php?id=533 https://github.com/file/file/commit/d04de269e0b06ccd0a7d1bf4974fed1d75be7d9e

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

在猜测编码之前查看更多大文件需要时间。然而,对于特定用例的选项是很好的,其中更好的猜测可能超过额外的时间/ io。

使用以下选项:

−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 5.04(2010)...希望有一天这个版本将从上游下来。


15
投票

所以人们说你不能和我理解你在提问和得到这样的答案时可能会感到沮丧。

如果你真的希望它以utf-8而不是us-ascii显示,那么你需要分两步完成。

第一:

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

第二:

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

那么如果你做一个文件-i你会看到新的字符集是utf-8。

希望能帮助到你。


11
投票

我认为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,它在大多数欧洲语言中很受欢迎。


2
投票

US-ASCII和UTF-8之间没有区别,因此无需转换它。但是这里有一点提示,如果你在录制时遇到特殊字符问题。

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

例:

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

这有助于我处理奇怪类型的引号,这些引号总是打破了charset编码过程。


2
投票

这是一个脚本,它将找到与您传递的模式匹配的所有文件,然后将它们从当前文件编码转换为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

1
投票

您可以使用file -i file_name来检查原始文件格式的确切内容。

完成后,您可以执行以下操作:

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

1
投票

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

我不知道如何确定编码,而不是其他人在这里建议的。

© www.soinside.com 2019 - 2024. All rights reserved.