web-dev-qa-db-ja.com

US-ASCIIからUTF-8への強制エンコード(iconv)

大量のファイルを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の場合、ファイルエンコーディング(文字セット)がUTF-8に切り替わります。

私の場合、とにかくiconvにファイルをUTF-8にトランスコードさせます。非ASCII文字が含まれているかどうか。

注:理由は、私のPHPコード(非ASCIIファイル...)が非ASCII文字列を処理しているため、文字列が適切に解釈されない(フランス語)ためです。

Ilétait une fois ... l'hommesérieanimée mythique d'Albert

Barillé(Procidis)、1ère

...

編集

  • US-ASCII-is-UTF-8のサブセット(下記の Nedの答え を参照)
  • US-ASCIIファイルが実際にUTF-8でエンコードされているという意味
  • 私の問題はどこかから来ました
54
eightyfive

ASCIIはUTF-8のサブセットであるため、すべてのASCIIファイルはすでにUTF-8エンコードされています。 ASCIIファイル内のバイトと「UTF-8にエンコード」した結果として生じるバイトは、まったく同じバイトになります。それらの間に違いはないので、何もする必要はありません。

問題は、ファイルが実際にはASCIIではないことです。使用しているエンコーディングを判断し、適切にトランスコードする必要があります。

61
Ned Batchelder

簡潔な答え

  • 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はオーバーロードされており、混乱を招きます。

7ビットASCIIには128文字(00-7Fまたは10進数で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

extended ascii(またはhigh ascii)という用語は、標準の7ビット_を含む8ビット以上の文字エンコーディングを指します。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

今日学んだ1つの教訓は、ファイルの文字エンコーディングの正しい解釈を常に与えるためにfileを信頼できないことです。

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

このコマンドは、ファイルが何であるかだけを示し、ファイルが何であるかを示しません(ファイルがコンテンツを見る場合)。内容が一致しないファイルにマジックナンバーを挿入することで、プログラムをだますのは簡単です。したがって、コマンドは特定の状況以外ではセキュリティツールとして使用できません。

fileはファイル内で型を暗示するマジックナンバーを探しますが、これらは間違っている可能性があり、正確性を保証するものではありません。 fileは、ファイル内のバイトを調べることで文字エンコードを推測しようとします。基本的にfileには、ファイルの種類とエンコーディングを推測するのに役立つ一連のテストがあります。

私のファイルは大きなCSVファイルです。 fileは、このファイルをus-asciiエンコードとして報告します。これはWRONGです。

$ 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は、ファイルに10万行を超えるまで表示されません。これが、fileがファイルエンコーディングがUS-ASCIIでないことを認識しない理由だと思います。

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

私はMacにいるので、PCREのgrepを使用しています。 gnu grepでは、-Pオプションを使用できます。あるいは、Mac上で、gnu grepを取得するためにcoreutilsを(homebrewなどを介して)インストールできます。

fileのソースコードを掘り下げたことはなく、マニュアルページではテキストエンコーディングの検出について詳しく説明していませんが、エンコーディングを推定する前に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から始まる2バイトを取るように指示します。特別な(非7ビットASCII ) "。"で表される文字で、次のバイトは "M" ...ですので、これは1バイト文字エンコードです。

$ 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で開くと、Öとして表示されます。なぜなら、vimは、fileよりも文字エンコード(この場合)のguessingの方が優れているからです。

したがって、私のファイルは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

間違いなく変更。 2バイトの非7ビットASCII(右側の「。」で表される)があり、2バイトの16進コードが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であった最初のウムラウトは、それがiconvに伝えたことであるため、ISO-8859-1として解釈されました。 2番目のウムラウトは、d6からc3 96に正しく変換されます。

もう一度試してみますが、今回は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

いいね。新しいウムラウトと古いウムラウトの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のこの特定の制限に対処するハック(失敗しやすい)は、ファイルを短くして、特殊文字がファイルの初期に現れるようにすることで、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

更新

Christos Zoulasはfileを更新して、見られるバイト数を設定可能にしました。ある日、機能のリクエストのターンアラウンド、素晴らしい!

http://bugs.gw.com/view.php?id=5https://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)があります...いつかこのリリースでアップストリームからダウンすることを願っています。

36
mattpr

だから人々はあなたができないと言うし、私はあなたが質問をしてそのような答えを得るときにあなたがイライラするかもしれないことを理解しています。

Us-asciiではなくutf-8で表示したい場合は、2ステップで行う必要があります。

最初 :

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

2番目:

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

ファイル-iを実行すると、新しい文字セットがutf-8であることがわかります。

それが役に立てば幸い。

16
Mathieu

Nedが問題の核心を持っている -あなたのファイルは実際にはASCIIではない。試してみる

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

私はあなたが実際に iso-8859-1 を使用していると推測していますが、ほとんどのヨーロッパ言語で人気があります。

11
sarnold

US-ASCIIとUTF-8に違いはないため、再変換する必要はありません。ただし、再コーディング中に特殊文字に問題がある場合は、ここに少しヒントを示します。

Source-charset-Parameterの後に// TRANSLITを追加します。

例:

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

これは、文字セットの再エンコードプロセスを常に壊してしまう、奇妙なタイプの引用に役立ちます。

2
suther

次に、渡すパターンに一致するすべてのファイルを検索し、現在のファイルエンコーディングからutf-8に変換するスクリプトを示します。エンコードがus-asciiの場合、utf-8のサブセットであるため、us-asciiとして表示されます。

#!/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
2
Pytry

file -i file_nameを使用して、元のファイル形式を正確に確認できます。

それを取得したら、次のことができます。

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

誤ってファイルを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

ここで他の人が提案したもの以外のエンコーディングを決定する方法がわかりません。

1
Schabry