Самый аккуратный способ удалить линейные разбивки в Perl

StackOverflow https://stackoverflow.com/questions/881779

  •  22-08-2019
  •  | 
  •  

Вопрос

Я поддерживаю сценарий, который может получить свой вклад из различных источников, и работает над ним по линии. В зависимости от фактического используемого источника, Line Breaks может быть в стиле UNIX, в стиле Windows или даже для некоторого агрегированного входа, смешанного (!).

При чтении из файла это идет примерно так:

@lines = <IN>;
process(\@lines);

...

sub process {
    @lines = shift;
    foreach my $line (@{$lines}) {
        chomp $line;
        #Handle line by line
    }
}

Итак, что мне нужно сделать, так это заменить Chomp чем-то, что удаляет либо линейные разбивки в стиле UNIX, либо в стиле Windows. Я приду в слишком много способов решения этого, один из обычных недостатков Perl :)

Каково ваше мнение о самом аккуратном способе отмены общих линейных разбил? Что будет наиболее эффективным?

Изменить: небольшое разъяснение - метод «процесс» получает список линий откуда, не чтение из файла. Анкет Каждая строка могла бы иметь

  • Никаких зацепленных линейных разбил
  • Linebreaks в стиле Unix
  • LineBreaks в стиле Windows
  • Просто возврат перевозки (когда оригинальные данные имеют линейные разбивки в стиле Windows и читаются с $/ = ' n')
  • Агрегированный набор, где линии имеют разные стили
Это было полезно?

Решение

После копания через Перлр Документы немного представлю свое лучшее предложение, которое, кажется, работает довольно хорошо. Perl 5.10 добавил класс символов r в качестве обобщенного нарушения линии:

$line =~ s/\R//g;

Это то же самое, что:

(?>\x0D\x0A?|[\x0A-\x0C\x85\x{2028}\x{2029}])

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

Другие советы

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

sub clean {

    my $text = shift;

    $text =~ s/\n//g;
    $text =~ s/\r//g;

    return $text;
}

Это может быть не модным, но этот метод работал безупречным для меня годами.

Чтение Перлпорт Я бы предложил что -то вроде

$line =~ s/\015?\012?$//;

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

$line =~ s/[\r\n]+//g;

Примечание с 2017 года: File :: Slurp не рекомендуется из -за ошибок в проектировании и невозможных ошибок. Использовать File :: Splurper или же Путь :: Крошечный вместо.

Расширение вашего ответа

use File::Slurp ();
my $value = File::Slurp::slurp($filename);
$value =~ s/\R*//g;

Файл :: Slurp устраняет файл io io и просто возвращает для вас строку.

ПРИМЕЧАНИЕ

  1. Важно отметить добавление /g , без этого, с учетом многострочной строки, она только заменит первый оскорбительный персонаж.

  2. Также удаление $, что является избыточным для этой цели, как мы хотим раздеться все разрывы линии, а не просто разбиты линии перед тем, что подразумевалось под $ На этой ОС.

  3. В многострочной строке, $ соответствует конец нить и это было бы проблематично).

  4. Точка 3 означает, что точка 2 сделана с предположением, что вы также хотите использовать /m В противном случае '$' было бы в основном бессмысленным для чего -либо практического в строке с> 1 строками или, выполнять обработку одной линии, ОС, которая на самом деле понимает $ и удается найти \R* это продолжается $

Примеры

while( my $line = <$foo> ){
      $line =~ $regex;
}

Учитывая приведенную выше нотацию, ОС, которая не понимает, какие ваши файлы ' n' или ' r' делимитеры, в сценарии по умолчанию с набором разделителей ОС по умолчанию $/ приведет к чтению всего вашего файла в качестве одной смежной строки (если в вашей строке нет делимитов $ ОС, где это будет разграничить это)

Так что в этом случае все эти корпорации бесполезны:

  • /\R*$// : Будет только стереть последнюю последовательность \R в файле
  • /\R*// : Будет только стереть первую последовательность \R в файле
  • /\012?\015?// : Когда только стереть первое 012\015 , \012 , или же \015 последовательность, \015\012 приведет к любому \012 или же \015 излучение.

  • /\R*$// : Если в файле не было никаких байтовых последовательностей ' 015 $ osdelimiter', то тогда НЕТ LineBreaks будут удалены, за исключением собственных ОС.

Казалось бы, никто не получает то, о чем я говорю, так что вот пример кода, то есть протестирован к НЕТ Снимите линейные подачи. Запустите его, вы увидите, что он оставляет линейную корпус.

#!/usr/bin/perl 

use strict;
use warnings;

my $fn = 'TestFile.txt';

my $LF = "\012";
my $CR = "\015";

my $UnixNL = $LF;
my $DOSNL  = $CR . $LF;
my $MacNL  = $CR;

sub generate { 
    my $filename = shift;
    my $lineDelimiter = shift;

    open my $fh, '>', $filename;
    for ( 0 .. 10 )
    {
        print $fh "{0}";
        print $fh join "", map { chr( int( rand(26) + 60 ) ) } 0 .. 20;
        print $fh "{1}";
        print $fh $lineDelimiter->();
        print $fh "{2}";
    }
    close $fh;
}

sub parse { 
    my $filename = shift;
    my $osDelimiter = shift;
    my $message = shift;
    print "Parsing $message File $filename : \n";

    local $/ = $osDelimiter;

    open my $fh, '<', $filename;
    while ( my $line = <$fh> )
    {

        $line =~ s/\R*$//;
        print ">|" . $line . "|<";

    }
    print "Done.\n\n";
}


my @all = ( $DOSNL,$MacNL,$UnixNL);
generate 'Windows.txt' , sub { $DOSNL }; 
generate 'Mac.txt' , sub { $MacNL };
generate 'Unix.txt', sub { $UnixNL };
generate 'Mixed.txt', sub {
    return @all[ int(rand(2)) ];
};


for my $os ( ["$MacNL", "On Mac"], ["$DOSNL", "On Windows"], ["$UnixNL", "On Unix"]){
    for ( qw( Windows Mac Unix Mixed ) ){
        parse $_ . ".txt", @{ $os };
    }
}

Для ЧЕТКО Необработанный вывод, см. Здесь: http://pastebin.com/f2c063d74

Обратите внимание, что есть определенные комбинации, которые, конечно, работают, но они, вероятно, те, которые вы сами надо, протестированы.

Обратите внимание, что на этом выводе все результаты должны иметь форму >|$string|<>|$string|< с Нет линейных кормов считаться допустимым выводом.

а также $string имеет общую форму {0}$data{1}$delimiter{2} Где во всех источниках вывода должен быть либо:

  1. Ничего между {1} а также {2}
  2. Только |<>| между {1} а также {2}

В вашем примере вы можете просто пойти:

chomp(@lines);

Или же:

$_=join("", @lines);
s/[\r\n]+//g;

Или же:

@lines = split /[\r\n]+/, join("", @lines);

Используя их непосредственно в файле:

perl -e '$_=join("",<>); s/[\r\n]+//g; print' <a.txt |less

perl -e 'chomp(@a=<>);print @a' <a.txt |less

Чтобы расширить ответ Теда Кэмброна выше, и что -то, что здесь не было адресовано: если вы удалите все разрывы строк без разбора из куски введенного текста, вы в конечном итоге получите параграфы, столкнувшись друг с другом без пробелов, когда вы выводите этот текст позже. Это то, что я использую:

sub cleanLines{

    my $text = shift;

    $text =~ s/\r/ /; #replace \r with space
    $text =~ s/\n/ /; #replace \n with space
    $text =~ s/  / /g; #replace double-spaces with single space

    return $text;
}

Последняя замена использует модификатор G 'Greedy, поэтому он продолжает находить двойные пространства, пока не заменит их все. (Эффективно заменить что -нибудь еще, что одно пространство)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top