Регулярное выражение Perl найти и заменить

Я новичок в Perl, и я пытаюсь найти и заменить. У меня есть большой CSV-файл (на самом деле разделенный точкой с запятой). Некоторые числа (целые и десятичные) в файле имеют отрицательный символ после числа. Мне нужно переместить отрицательный знак перед числом.

Например: изменить

ABC;10.00-;XYZ

to

ABC;-10.00;XYZ

Я не уверен, как это сделать в perl. Может кто-нибудь помочь?

С уважением, Ананд


person Anand    schedule 01.08.2011    source источник


Ответы (3)


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

Этот скрипт будет принимать входные файлы в качестве аргументов и записывать исправленные файлы с расширением .new.

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

use strict;
use warnings;
use autodie;
use Text::CSV;

my $out_ext = ".new";
my $csv = Text::CSV->new( { 
        sep_char => ";",
        #   keep_meta_info => 1,
        binary => 1,
        eol => $/,
    } ) or die "" . Text::CSV->error_diag();

for my $arg (@ARGV) {
    open my $input, '<', $arg;
    open my $output, '>', $arg . $out_ext;
    while (my $row = $csv->getline($input)) {
        for (@$row) {
            s/([0-9\.]+)\-$/-$1/;
        }
        $csv->print($output, $row);
    }
}
person TLP    schedule 01.08.2011

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

while( my $line = <STDIN> )
{
    chop( $line );
    my @rec = split( ';', $line );
    map( s/^(\d*\.?\d+)\-$/-$1/, @rec );
    print join(';',@rec) . "\n";
}

Если вам нужно беспокоиться об экранировании и заключении в кавычки, используйте Text:: CSV_XS вместо операций <STDIN>, split и join

person Sodved    schedule 01.08.2011
comment
chop, вероятно, должно быть chomp. Вы можете быть осторожны с использованием split в файле csv, так как это может привести к нежелательным изменениям в файле. Из perldoc perlfunc: By default, empty leading fields are preserved, and empty trailing ones are deleted. - person TLP; 01.08.2011

В общем, команда замены s/old/new/flags:

s/(           # start a capture group
    \d+       # first part of the number
    (\.\d+)?  # possibly a decimal dot and the fractional part
  )-          # end capture group, match the minus sign
 /-$1/gx      # move minus to the front

Флаг g означает «глобальный» (заменяет все вхождения), а x — «расширенную разборчивость» (допускает пробелы и комментарии в шаблоне). Вы должны протестировать выражение на своих данных, чтобы увидеть, какие угловые случаи вы могли пропустить, обычно требуется несколько итераций, чтобы получить правильный. Образцы:

$ echo "10.5-;10-;0-;a-" | perl -pe 's/(\d+(\.\d+)?)-/-$1/g'
-10.5;-10;-0;a-

См. также perldoc perlop (введите слово «замена», чтобы перейти к нужному разделу).

person zoul    schedule 01.08.2011
comment
Не испортит ли это даты? Например. 2011-01-01 превратится в -2011-0101. - person TLP; 01.08.2011
comment
Да, это вполне возможно. Вот почему я сказал, что всегда сначала пробую шаблон на реальных данных, чтобы увидеть, что я мог упустить. Выполнение нескольких итераций с простой заменой регулярных выражений IMO часто проще, чем поиск более общего решения. - person zoul; 01.08.2011
comment
Если это не большой файл, как сказал ОП, в этом случае вам нужно быть довольно осторожным. Однако вы правы в том, что мы не можем настроить код так, как это может сделать сам ОП. - person TLP; 01.08.2011
comment
Например, разумным может быть добавление предпросмотра с точкой с запятой в начале и в конце. - person TLP; 01.08.2011
comment
Да, что это хорошая идея. Большой файл на самом деле не означает сложный. Возможно, файл представляет собой дамп какого-то измерения или чего-то еще, и формат очень простой, только данных много. Но я думаю мы поняли друг друга, остальное уже на афише выяснять. - person zoul; 01.08.2011