Лучший способ удалить разрывы строк в Perl - PullRequest
49 голосов
/ 19 мая 2009

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

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

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

...

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

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

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

Редактировать: Небольшое уточнение - метод 'process' откуда-то получает список строк, не обязательно читаемых из файла . Каждая строка может иметь

  • Нет завершающих разрывов строк
  • Разрывы строк в стиле Unix
  • переносы строк в стиле Windows
  • Just Carriage-Return (когда исходные данные имеют разрывы строк в стиле Windows и читаются с помощью $ / = '\ n')
  • Агрегированный набор, в котором линии имеют разные стили

Ответы [ 7 ]

86 голосов
/ 19 мая 2009

Немного покопавшись в документах perlre , я покажу свое лучшее предложение, которое пока работает довольно неплохо. В Perl 5.10 добавлен класс символов \ R в качестве обобщенного перевода строки:

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

Это так же, как:

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

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

12 голосов
/ 19 февраля 2011

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

sub clean {

    my $text = shift;

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

    return $text;
}

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

7 голосов
/ 19 мая 2009

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

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

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

6 голосов
/ 19 мая 2009

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

на ваш ответ

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

File :: Slurp абстрагирует материал File IO и просто возвращает строку для вас.

Примечание

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

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

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

  4. Пункт 3 означает, что пункт 2 сделан с предположением, что вы также захотите использовать /m в противном случае «$» будет в принципе бессмысленным для чего-либо практического в строке с> 1 строкой, или, делая однострочная обработка - операционная система, которая на самом деле понимает $ и находит \R*, которая обрабатывает $

Примеры

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

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

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

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

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

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

#!/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 };
    }
}

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

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

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

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

  1. Ничего между {1} и {2}
  2. только |<>| между {1} и {2}
6 голосов
/ 19 мая 2009
$line =~ s/[\r\n]+//g;
2 голосов
/ 28 мая 2014

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

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
1 голос
/ 19 августа 2016

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

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', поэтому он продолжает находить двойные пробелы, пока не заменит их все. (Эффективно заменяя все, что больше, чем один пробел)

...