Я пытаюсь перекодировать кучу файлов из US-ASCII в UTF-8.
Thing - мои исходные файлы, кодированные US-ASCII, что делает невозможным преобразование. По-видимому, это происходит потому, что ASCII является подмножеством UTF-8...
True. Если я введу в файл не-ASCII-символ и сохраню его, скажем, с Eclipse, кодировка файла (charset) переключается на UTF-8.
Примечание. Причина в том, что мой PHP-код (файлы, отличные от ASCII...) имеет дело с некоторой строкой, отличной от ASCII, что приводит к тому, что строки не могут быть хорошо интерпретированы (французский):
Ответ 2
Короткий ответ
-
file
только догадывается о кодировке файла и может быть неправильным.
- вы можете использовать
hexdump
для просмотра байтов текста без 7-бит-ascii и сравнения с кодовыми таблицами для общих кодировок (7-бит-ascii, iso-8859- *, utf-8), чтобы решить что такое кодировка.
-
iconv
будет использовать любую кодировку ввода/вывода, которую вы укажете вне зависимости от содержимого файла. Если вы укажете неправильную кодировку ввода, выход будет искажен.
- даже после запуска
iconv
, file
может не сообщать о каких-либо изменениях из-за ограниченного способа, с помощью которого file
пытается угадать кодировку. Для конкретного примера см. Мой длинный ответ.
Длинный ответ
Я столкнулся с этим сегодня и наткнулся на ваш вопрос. Возможно, я могу добавить немного больше информации, чтобы помочь другим людям, столкнувшимся с этой проблемой.
Во-первых, термин ASCII перегружен, и это приводит к путанице (включая меня).
7-разрядный ASCII включает только 128 символов (00-7F или 0-127 в десятичной форме). 7-разрядный ASCII также упоминается как US-ASCII.
https://en.wikipedia.org/wiki/ASCII
Кодировка UTF-8 использует ту же кодировку, что и 7-разрядный ASCII для первых 128 символов. Таким образом, текстовый файл, содержащий только символы из этого диапазона из первых 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-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 закодированный, 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
В моем файле есть umlauts (т.е....). Первый не-7-бит-ascii не отображается до более чем 100k строк в файл. Я подозреваю, что именно поэтому file
не понимает, что кодировка файла не является US-ASCII.
$ pcregrep -no '[^\x00-\x7F]' source-file | head -n1
102321:�
(Я нахожусь на Mac, поэтому использую PCRE grep
. С gnu grep вы можете использовать опцию -P
.)
Я не вкопался в исходный код file
, и man-страница подробно не обсуждает обнаружение текстового кодирования, но я предполагаю, что 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 char находится в позиции 85 в строке 102321. Я беру эту строку и говорю hexdump
, чтобы взять два байта, начиная с позиции 85. Вы можете увидеть специальный (не-7 -bit-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
, он отобразится как... потому что 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) 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. К сожалению! Просто идет, чтобы показать, как легко заставить кодировки прикручиваться.
Попробуйте преобразовать наш новый тестовый файл с помощью umlaut спереди и посмотреть, что произойдет.
$ 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
для ввода Ö вместо 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
Boom! Мораль истории. Не доверяйте 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
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)... надеюсь, что когда-нибудь эта версия сделает это с восходящего потока.