Почему я не могу использовать функцию карты для создания хорошего хэша из простого файла данных в Perl?

Пост обновлен. Пожалуйста, перейдите к части решения, если вы уже прочитали опубликованный вопрос. Спасибо!

Вот минимизированный код, демонстрирующий мою проблему:

Файл входных данных для теста был сохранен встроенным блокнотом Windows в кодировке UTF-8. Он имеет следующие три строки:

abacus  æbәkәs
abalone æbәlәuni
abandon әbændәn

Файл сценария Perl также был сохранен встроенным блокнотом Windows в кодировке UTF-8. Он содержит следующий код:

#!perl -w

use Data::Dumper;
use strict;
use autodie;
open my $in,'<',"./hash_test.txt";
open my $out,'>',"./hash_result.txt";

my %hash = map {split/\t/,$_,2} <$in>;
print $out Dumper(\%hash),"\n";
print $out "$hash{abacus}";
print $out "$hash{abalone}";
print $out "$hash{abandon}";

В выводе хеш-таблица выглядит нормально:

$VAR1 = {
          'abalone' => 'æbәlәuni
',
          'abandon' => 'әbændәn',
          'abacus' => 'æbәkәs
'
        };

Но на самом деле это не так, потому что я получаю только два значения вместо трех:

æbәlәuni
әbændәn

Perl выдает следующее предупреждающее сообщение:

Use of uninitialized value $hash{"abacus"} in string at C:\test2.pl line 11, <$i n> line 3.

в чем проблема? Может кто-нибудь любезно объяснить? Спасибо.

Решение

Миллионы благодарностей всем вам, ребята :) Теперь, наконец, виновник найден, и проблема становится решаемой :) Как проницательно заметил @Sinan, теперь я на 100% уверен, что виновником проблемы, которую я описал выше, являются два байт спецификации, которые Блокнот добавил в мой файл данных, когда он был сохранен как UTF-8, и которые Perl почему-то не обрабатывает должным образом. Хотя многие предлагали мне использовать «‹:utf8» и «>:utf8» для чтения и записи файлов, дело в том, что эти конфигурации utf-8 не решают проблему. Вместо этого они могут вызвать некоторые другие проблемы.

Чтобы действительно решить проблему, все, что мне действительно нужно, это добавить одну строку кода, чтобы заставить Perl игнорировать спецификацию:

#!perl -w

use Data::Dumper;
use strict;
use autodie;

open my $in,'<',"./hash_test.txt";
open my $out,'>',"./hash_result.txt";

seek $in,3,0; # force Perl to ignore the BOM!
my %hash = map {split/\t/,$_,2} <$in>;
print $out Dumper(\%hash);
print $out $hash{abacus};
print $out $hash{abalone};
print $out $hash{abandon};

Теперь результат именно то, что я ожидал:

$VAR1 = {
          'abalone' => 'æbәlәuni
',
          'abandon' => 'әbændәn',
          'abacus' => 'æbәkәs
'
        };
æbәkәs
æbәlәuni
әbændәn

Обратите внимание, что сценарий сохраняется в кодировке UTF-8, и код не должен включать какие-либо метки utf-8, поскольку входной и выходной файлы предварительно сохранены в кодировке UTF-8.

Наконец, еще раз спасибо всем вам. И спасибо, @Sinan, за проницательное руководство. Без твоей помощи я бы оставался во тьме бог знает сколько времени.

Примечание Чтобы уточнить, если я использую:

open my $in,'<:utf8',"./hash_test.txt";
open my $out,'>:utf8',"./hash_result.txt";

my %hash = map {split/\t/,$_,2} <$in>;
print $out Dumper(\%hash);
print $out $hash{abacus};
print $out $hash{abalone};
print $out $hash{abandon};

Вывод таков:

$VAR1 = {
          'abalone' => "\x{e6}b\x{4d9}l\x{4d9}uni
",
          'abandon' => "\x{4d9}b\x{e6}nd\x{4d9}n",
          "\x{feff}abacus" => "\x{e6}b\x{4d9}k\x{4d9}s
"
        };
æbәlәuni
әbændәn

И предупреждающее сообщение:

Use of uninitialized value in print at C:\hash_test.pl line 13,  line 3.

person Community    schedule 19.11.2009    source источник
comment
Ваш код отлично работает для меня, хотя я бы chomp внутри карты {}   -  person Matteo Riva    schedule 19.11.2009
comment
@ Синан, это странно. Я уверен, что записи разделены вкладками, поэтому я использую \t. Но когда я использую \s, как предложил apbianco, все становится хорошо. Ну, это действительно странно!   -  person Mike    schedule 19.11.2009
comment
@Kemper, так ты проверил код на своей машине, и все в порядке? Я вручную набрал вкладки, чтобы разделить записи, но код не работает для записи первой строки.   -  person Mike    schedule 19.11.2009
comment
@ Синан, о нет, проблема все еще сохраняется.   -  person Mike    schedule 19.11.2009
comment
@Mike Я вставил текст в файл и запустил программу точно так, как было опубликовано. Все элементы хеша есть. Нет неинициализированных значений предупреждения.   -  person Sinan Ünür    schedule 19.11.2009
comment
@all, если код отлично работает в других системах, то, думаю, виновником может быть моя система.   -  person Mike    schedule 19.11.2009
comment
@Sinan, спасибо, не уверен на 100%, но теперь я думаю, что это, вероятно, какая-то проблема несовместимости с ОС. Возможно, что-то связанное с обработкой кодировки ОС.   -  person Mike    schedule 19.11.2009
comment
@Mike: Это не имеет ничего общего с несовместимостью с ОС. Обратите внимание на лишний пробел в ' abacus' в вашем выводе Dumper (который не был виден до того, как я отредактировал ваш пост). Смотрите мой обновленный ответ.   -  person Sinan Ünür    schedule 19.11.2009
comment
@ Синан, лишний пробел? Я посмотрю еще раз. Спасибо!   -  person Mike    schedule 19.11.2009
comment
@Mike: убедитесь, что файл сохранен в кодировке UTF-8. Убедитесь, что вы читаете его как UTF-8. Убедитесь, что вывод имеет формат UTF-8. Сохранение файла в формате UTF-8 и чтение его как такового решит проблему. Запись вывода дампера в дескриптор UTF-8 поможет вам диагностировать, что происходит. Я почти уверен, что дополнительное пространство в ' abacus' — это спецификация.   -  person Sinan Ünür    schedule 19.11.2009
comment
@Синан, спасибо за все подсказки. Я читаю запись wikiepdia для спецификации.   -  person Mike    schedule 19.11.2009
comment
@Mike Также может быть полезно проверить, что находится в вашем входном файле и что в вашем выходном файле.   -  person Sinan Ünür    schedule 19.11.2009


Ответы (5)


Я нахожу предупреждающее сообщение немного подозрительным. Он говорит вам, что дескриптор файла $in находится в строке 3, хотя он должен быть в строке 4 после прочтения последней строки.

Когда я попробовал ваш код, я сохранил входной файл с помощью GVim, который настроен в моей системе для сохранения в формате UTF-8, и я не увидел проблемы. Теперь, когда я попробовал это с Блокнотом, глядя на выходной файл, я вижу:

"\x{feff}abacus" => "\x{e6}b\x{4d9}k\x{4d9}s
"

где \x{feff} — это спецификация.

В вашем выводе Dumper есть ложный пробел перед abacus (где вы не указали :utf8 для дескриптора вывода).

Как я упоминал изначально (потеряно из-за бесчисленных правок в этом посте, спасибо за напоминание hobbs), укажите '<:utf8' при открытии входного файла.

person Sinan Ünür    schedule 19.11.2009
comment
@Синан, спасибо. Но проблема сохраняется, и предупреждающее сообщение остается прежним. Как я заметил, в моей системе, когда файл данных закодирован как utf8, а сценарий Perl также сохранен как utf-8, мне не нужно использовать формат ‹:utf8. - person Mike; 19.11.2009
comment
@Sinan, чтобы решить эту проблему с отсутствием записи в первой строке, кажется, мне нужно добавить пустую строку с пробелом и табуляцией, а также еще один пробел и \n. - person Mike; 19.11.2009
comment
@ Синан, виновата система. Я сохранил сценарий, файл данных и выходной файл как системную кодировку по умолчанию, GB2312, и хотя некоторые символы не будут отображаться должным образом, все хеш-элементы присутствуют. - person Mike; 19.11.2009
comment
@Синан, спасибо! Да, именно \x{fefe} вызывает проблему. Они вставляются в начале первой строки, чтобы указать, что данные закодированы в utf, но по какой-то причине моя ОС не обрабатывает их правильно. Я почти уверен, что сделал все необходимое, чтобы данные считывались в кодировке utf-8. - person Mike; 19.11.2009
comment
Блокнот добавляет спецификацию при сохранении в формате UTF-8. Чтобы прочитать это правильно, вам нужно открыть с помощью '<:utf8'. Это 100% так и должно быть и нет никаких проблем. - person hobbs; 19.11.2009
comment
@hobbs, спасибо. Но дело в том, что добавление «‹: utf8» не решает мою проблему. Пожалуйста, прочитайте мой обновленный пост. Спасибо. - person Mike; 20.11.2009
comment
@Синан, еще раз спасибо. Я решил проблему. Пожалуйста, прочтите мой обновленный пост. Спасибо :) - person Mike; 20.11.2009

Если вы хотите читать/записывать файлы UTF8, убедитесь, что вы действительно читаете их как UTF8.

#! /usr/bin/env perl
use Data::Dumper;
open my $in,  '<:utf8', "hash_test.txt";
open my $out, '>:utf8', "hash_result.txt";

my %hash = map { chomp; split ' ', $_, 2 } <$in>;
print $out Dumper(\%hash),"\n";
print $out "$hash{abacus}\n";
print $out "$hash{abalone}\n";
print $out "$hash{abandon}\n";

Если вы хотите, чтобы он был более надежным, рекомендуется использовать :encoding(utf8) вместо :utf8 для чтения файла.

open my $in, '<:encoding(utf8)', "hash_test.txt";

Прочтите PerlIO для получения дополнительной информации.

person Brad Gilbert    schedule 19.11.2009
comment
@Брэд, спасибо. Но я уже перепробовал все эти настройки utf-8, вроде не помогают. - person Mike; 19.11.2009
comment
Вы уверены, что исходный текст в UTF8? - person Brad Gilbert; 19.11.2009
comment
@Брэд, да, я на 100% уверен, что исходный текст в UTF8. Без кодировки UTF-8 моя ОС просто не будет правильно отображать эти символы. Пожалуйста, прочитайте мой обновленный пост. - person Mike; 20.11.2009

Я думаю, что ваш ответ может сидеть прямо перед вами. Вывод из Data::Dumper, который вы опубликовали:

$VAR1 = {
          'abalone' => 'æbәlәuni
',
          'abandon' => 'әbændәn',
          'abacus' => 'æbәkәs
'
        };

Обратите внимание на символ между ' и abacus? Вы пытались получить доступ к третьему значению через $hash{abacus}. Это неверно из-за этого символа перед abacus в хеше Dumper(). Вы можете попробовать подключить его к циклу, который должен позаботиться об этом:

foreach my $k (keys %hash) {
  print $out $hash{$k};
}
person Jack M.    schedule 19.11.2009

сплит/\с/ вместо сплит/\т/

person apbianco    schedule 19.11.2009
comment
split /\s/ отличается от split ' '. К сожалению, многие люди используют первое, а не второе, когда имеют в виду второе. - person Sinan Ünür; 19.11.2009
comment
@apbianco, проблема все еще сохраняется. - person Mike; 19.11.2009
comment
@all, это действительно расстраивает :( Может быть, это потому, что я работаю в Windows XP (китайская версия) и есть несовместимость некоторых кодировок? Но я уже принял меры предосторожности и файл данных как UTF-8. - person Mike; 19.11.2009
comment
Подождите: 'abacus' =› 'æbәkәs' -- вы вырезали и вставляли или печатали вывод? Это пробел в конце `æbәkәs'? - person apbianco; 19.11.2009
comment
@apbianco На самом деле, решающим фактором, который может привести к предупреждению, является дополнительный символ пробела в ' abacus'. Этого не было видно до того, как я переформатировал его сообщение, чтобы использовать <pre>. - person Sinan Ünür; 19.11.2009
comment
@Svante Спасибо за исправление. Редактирование в поле для комментариев подвержено ошибкам, и я забыл это исправить. Кроме того, в качестве юмористического примечания Некоторые недовольные не будут иметь ничего из этого, утверждая, что в Великобритании считается совершенно правильным использовать отличное от предложной конструкции. Итак? straightdope.com/columns/ read/2295/ Один из моих учителей английского языка в старшей школе был выпускником Оксфорда. Я виню в этом его. - person Sinan Ünür; 20.11.2009

Работает на меня. Вы уверены, что ваш пример соответствует вашему фактическому коду и данным?

person sorpigal    schedule 19.11.2009
comment
@Sorpigal, спасибо за попытку помочь. Я полагаю, что этот ответ был отклонен, потому что он был больше похож на комментарий, чем на ответ. - person Mike; 21.11.2009