Прочитайте один CSV-файл и запишите в другой CSV-файл после некоторого форматирования в perl - PullRequest
1 голос
/ 01 апреля 2020

Я пытаюсь манипулировать csv в perl.

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

use strict; 
use warnings 'all';

# Using Text::CSV file to allow 
# full CSV Reader and Writer 
use Text::CSV; 
use open ":std", ":encoding(UTF-8)";
my $file = $ARGV[0] or die; 

my $csv = Text::CSV->new ( 
{ 
    binary => 1, 
    auto_diag => 1, 
    sep_char => ', '
}); 

my $sum = 0; 

# Reading the file 
open(my $data, '<:encoding(utf8)', $file) or die; 

while (my $words = $csv->getline($data))  
{ 
    tr/\r\n//d for @$words; #removing new lines
    tr/,/;/ for @$words;    #replacing comma with semicolon
    $csv->combine(@$words);
    print $csv->string, "\n";
} 

# Checking for End-of-file 
if (not $csv->eof)  
{ 
    $csv->error_diag(); 
} 
close $data;

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

perl xyz.pl ${source_csv_file_name} > ${destination_processed_csv_file_name}

Я надеялся, что смогу использовать обработчик out csv в самом скрипте perl для записи вывода в другой файл. Я пробовал несколько способов, но продолжаю получать ту или иную ошибку. Ниже приведено кое-что, что я попробовал.

my $outcsv = Text::CSV->new ( { binary => 1, quote_char => "", escape_char => "\\" } );
open(my $data, '<:encoding(utf8)', $file) or die; 
open(my $fh, ">:encoding(utf8)", "new.csv") or die " new.csv: $!";
while (my $words = $csv->getline($data))  
{ 
    tr/\r\n//d for @$words;
    tr/,/;/ for @$words;
    $csv->combine(@$words);
    # Open a handle to the file "new.csv"
    $outcsv->print ($fh, $_) for @words;

    #print $csv->string, "\n";
} 

# Checking for End-of-file 
if (not $csv->eof)  
{ 
    $csv->error_diag(); 
} 
close $data;
close $fh or die "new.csv: $!";

Проблема - первый код, который я разместил выше, работает, но для записи файла я использовал оболочку оболочки. Теперь второй perl скрипт (я только разместил код, который отличается от первого), когда я запускаю, он завершается с ошибкой. Я понимаю эту ошибку, но не уверен, как ее исправить "Глобальный символ @words требует явного имени пакета в строке xyz.pl 29. Выполнение xyz.pl прервано из-за ошибок компиляции. Буду очень признателен, если кто-то сможет помочь здесь.

Спасибо

Ответы [ 3 ]

4 голосов
/ 01 апреля 2020

Я не уверен, что вам не удалось в первой программе, но здесь она немного упрощена и очищена

use strict; 
use warnings 'all';

use Text::CSV; 
use open ":std", ":encoding(UTF-8)";

my $file = $ARGV[0] or die "Usage: $0 filename\n";

my $csv = Text::CSV->new({ binary => 1, auto_diag => 1 }); 

open my $data,   '<',        $file or die $!; 
open my $fh_out, '>', 'new_'.$file or die $!; 

while (my $words = $csv->getline($data))  
{ 
    tr/\r\n//d for @$words;
    tr/,/;/    for @$words;

    $csv->say($fh_out, $words);
} 

Это работает правильно и хорошо протестировано с входным файлом, заимствованным из ответ Шона .

combine + string + print из вашей программы также работает для меня - но нет никаких причин для всего этого, так как print хорошо сочетает их (я использовал say, который также добавляет новую строку).

Несколько комментариев к программе в вопросе

  • После того, как вы использовали прагму open, как в вашей программе, тогда не делайте установить кодировку при открытии файлов. (И это должно быть :encoding(UTF-8), а не utf8. См. Об этом в Encode docs , и в этой Effective Perler article .)

  • При использовании die выведите фактическую ошибку, чаще всего в $! переменную

  • Два цикла выше явно менее эффективны, чем

    for (@$words) { tr/\r\n//d; tr/,/;/ }
    

    Я оставил их в виде двух циклов, чтобы указать отдельные этапы обработки.


Метод say в Text::CSV использованный выше был добавлен в модуль в какой-то момент, и версия, более старая, чем эта, не будет иметь его. Затем можно

  • Использовать метод print, а также установить eol в конструкторе, чтобы печатать переводы строк

    my $csv = Text::CSV->new ( { binary => 1, auto_diag => 1, eol => $/ });
    ...
    $csv->print($fh_out, $words);
    

    (есть и другие способы получения перевод строки см. в документах для eol)

  • Или не связывайтесь с конструктором, а добавьте этот перевод строки вручную

    $csv->print($fh_out, $words);
    print $fh_out "\n";
    
  • Или используйте окольный путь

    $csv->combine(@$words);
    print $fh_out $csv->string, "\n";
    

См. Документы для печати

1 голос
/ 01 апреля 2020

Модуль Text :: AutoCSV (устанавливается через менеджер пакетов ОС или любимый клиент CPAN) упрощает преобразование файлов CSV:

#!/usr/bin/env perl
use strict;
use warnings;
use Text::AutoCSV;

Text::AutoCSV->new(in_file => $ARGV[0],
                   out_file => $ARGV[1],
                   encoding => "UTF-8",
                   has_headers => 1, # Set to 0 if no header line
                   read_post_update_hr => \&normalize)->write();

sub normalize {
    my $hr = shift;
    for (values %$hr) {
        s/\r?\n//g;
        tr/,/;/;
    }
}

Пример:

$ cat input.csv
id,message
1,"a string, with a comma"
2,"another
with a newline"
3,blah
$ perl demo.pl input.csv new.csv
$ cat new.csv
id,message
1,"a string; with a comma"
2,"another with a newline"
3,blah
0 голосов
/ 01 апреля 2020

Вот код, который вызывает проблему:

while (my $words = $csv->getline($data))  
{ 
    tr/\r\n//d for @$words;
    tr/,/;/ for @$words;
    $csv->combine(@$words);
    # Open a handle to the file "new.csv"
    $outcsv->print ($fh, $_) for @words;

    #print $csv->string, "\n";
}

И, в комментарии, вы даете нам ошибку:

Глобальный символ @words требует явного имени пакета в d2l_preprocess_csv_files.pl строка 29.

Я предполагаю, что строка 29:

$outcsv->print ($fh, $_) for @words;

Вызов getline() дает вам ссылку на массив, который вы храните в $words. Если вы хотите рассматривать это как массив, вам нужно разыменовать его (@$words - как вы делаете в нескольких местах). Итак, на проблемной линии c вы просто забыли $. У вас нет массива с именем @words, вам нужно использовать $@words.

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