Perl предупреждает о неверной кодировке, даже если я не читаю проблемные данные из файла - PullRequest
1 голос
/ 07 июня 2019

Я пытаюсь прочитать строки из первой части файла, который содержит текстовый заголовок, закодированный в кодировке cp1252 , и содержит двоичные данные после определенного ключевого слова.

Проблема

Perl предупреждает о неверной кодировке в частях файла, которые я никогда не читал. Я создал пример в двух файлах, чтобы продемонстрировать проблему.

Содержимое linebug.pl :

#!/usr/bin/perl
use 5.028;
use strict;
use warnings;
open( my $fh, "<:encoding(cp1252)", "testfile" );
while( <$fh> ) {
    print;
    last if /Last/;
}

Hexdump из testfile , где байт 0x81 сразу после текста Wrong преднамеренно добавлен, поскольку он не является допустимым кодом cp1252:

46 69 72 73 74 0a         |First.|
4c 61 73 74 0a            |Last.|
42 75 66 66 65 72 0a      |Buffer.|
57 72 6f 6e 67 81 0a      |Wrong..|

Третья строка Буфер как раз там, чтобы прояснить, что я не читаю слишком далеко. Это допустимая строка между последней прочитанной строкой и «двоичными» данными.

Вот вывод, показывающий, что я читаю только две строки, но perl по-прежнему выдает предупреждение:

user@host$ perl linebug.pl
cp1252 "\x81" does not map to Unicode at ./linebug.pl line 6.
First
Last
user@host$

Как видно, моя программа читает и печатает первые две строки, а затем завершает работу. Он никогда не должен пытаться читать и интерпретировать что-либо еще, но я все равно получаю предупреждение о том, что \x81 не сопоставляется с Unicode.

Вопросы

  • Почему это предупреждает? Я не читаю строку. Предчувствие подсказывает мне, что оно пытается читать вперед, но почему оно пытается расшифровать?
  • Существует ли обходной путь или лучший способ обработки файлов, в которых кодировка меняется с одного раздела на другой?

Мне все еще нужно предупреждение при чтении начальных строк, если файл поврежден.

Ответы [ 2 ]

3 голосов
/ 07 июня 2019

Файлы не имеют понятия линий;они просто потоки байтов.Perl должен запросить количество байтов из файла у ОС и выяснить, где заканчивается строка, чтобы вернуть строку в программу.

Perl может запрашивать по одному байту за раз от ОС доимеет полную строку, но это было бы очень неэффективно.Существует много накладных расходов при выполнении системных вызовов.Таким образом, Perl запрашивает 8 КиБ одновременно.

Затем необработанные данные должны быть декодированы, прежде чем Perl сможет определить, где заканчивается строка, поскольку необработанный 0A не обязательно указывает на конец строки..

Подобно тому, почему человек не читает из файла по одному байту за раз, запрос декодера о декодировании только следующего символа будет неэффективным.Каждый раз при запуске и остановке декодирования возникают накладные расходы.Таким образом, Perl декодирует все данные, которые он читает, когда он читает.

Так что это означает, что Perl читает и декодирует больше, чем возвращает в программу.


Решениеобрабатывать файл как двоичный (потому что на самом деле это не текстовый файл, если кодировка изменяется по разделам) и выполнять декодирование самостоятельно.

Если вы работаете с однобайтовой кодировкой, такой как cp1252, вы можете продолжитьиспользуя readline (он же <$fh>).Однако вместо того, чтобы указывать Perl искать кодовую строку перевода строки (0A), вам нужно установить $/ для кодировки кодовой точки.Как это бывает, это также 0A для cp1252, поэтому никаких изменений не требуется.

use Encode qw( decode );

open( my $fh, "<:raw", $qfn )
   or die( "Can't open \"$qfn\": $!\n" );

while( <$fh> ) {
    $_ = decode( 'cp1252', $_ );      # :encoding(cp1252)
    s/\r\n\z/\n/ if $^O eq 'Win32';   # :crlf
    print;
    last if /Last/;
}

Если вы не использовали однобайтовую кодировку, возможно, вам придется переключиться на использование read.(Вы можете продолжать использовать readline для UTF-8 из-за способа, которым он спроектирован.) При использовании read точное решение зависит от нескольких особенностей (которые относятся к определению, сколько читать и сколько декодировать).

2 голосов
/ 07 июня 2019

Perl читает из файла в 8 кусочках по КиБ, так что за один раз читается больше, чем строка. Данные декодируются сразу после их чтения (поскольку поток должен быть декодирован, чтобы найти окончания строк), поэтому неожиданное кодирование замечается и предупреждается.

Один из способов справиться с этим: использовать небуферизованные операции чтения через sysread и читать меньшие порции за раз.

Подсчитайте количество прочитанных символов, и как только вы попадете в это место, вы сможете выполнить резервное копирование и продолжить чтение символа за раз, снова подсчитав их, чтобы определить точное место. См. этот пост для рабочего примера определения места, где сработало предупреждение.

Чтобы иметь возможность остановиться на этом, вы, вероятно, захотите выбросить die из обработчика $SIG{__WARN__} и иметь весь этот код в eval. Это позволит вам остановиться в том месте, откуда приходит предупреждение, и вернуть контроль.

Когда вы прочитали прямо до этого места, вы можете затем снова открыть файл в кодировке, подходящей для остальной части файла, и перейти к этому месту и прочитать остальное.

Я не могу написать и проверить все это прямо сейчас, надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...