Лучший способ удалить разрывы строк в Perl

48

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

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

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

...

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

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

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

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

  • Отсутствие прерывания строк
  • Линейные строки в стиле Unix
  • Линейные строки в стиле Windows
  • Just Carriage-Return (когда исходные данные имеют разрывы строк в стиле Windows и считываются с помощью $/= '\n')
  • Агрегированный набор, в котором строки имеют разные стили
  • 0
    Если оператор <> распознает символы новой строки, не будет ли шуметь?
  • 0
    См. Stackoverflow.com/questions/797993/…
Показать ещё 2 комментария
Теги:
line-breaks

7 ответов

80
Лучший ответ

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

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

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

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

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

  • 1
    Я призываю вас принять свой собственный ответ, если он работает на вас. \ R может работать не так, как ожидалось, на некоторых экзотических платформах (именно поэтому я и предложил аппаратный подход ранее), но если вы не пишете переносимый код, а просто хотите выполнить свою работу, все готово. Возможно, вы сначала захотите поместить тестовые файлы Кента Фредрика в свой код, потому что они действительно являются хорошим тестовым примером.
11

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

sub clean {

    my $text = shift;

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

    return $text;
}

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

  • 0
    Держу пари, что это решение, вероятно, более эффективно, чем условное регулярное выражение. Хороший ответ.
7

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

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

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

  • 1
    Потенциальные ошибки: 1) Нет / г, поэтому он не будет работать на многострочных строках. 2) $, поэтому он будет соответствовать только разделителям, которые находятся непосредственно перед концом строки. 3) фиксированный порядок \ 015 \ 012, так что если у них есть \ 012 \ 015, он съест только одного из них.
  • 2
    1) +2) Поскольку я не знаю, что находится внутри строк, я должен был предположить, что внутри могут быть символы новой строки, которые не следует удалять (например, записи в базе данных со столбцами данных с переносом строк). Мое намерение состояло в том, чтобы максимально приблизить поведение chomp (). 3) Я видел, как старые Mac используют только \ 015, а Windows по-прежнему использует \ 015 \ 012, но я еще не видел реальной системы, использующей \ 012 \ 015, поэтому я чувствовал, что этот порядок будет безопасным. ;)
Показать ещё 2 комментария
6

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

распространяющийся на ваш ответ

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

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

Примечание

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

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

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

  • Точка 3 означает, что точка 2 сделана с предположением, что вы также хотите использовать /m иначе "$" будет в основном бессмысленным для чего-либо практического в строке s > 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 Unprocessed см. здесь: http://pastebin.com/f2c063d74

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

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

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

  • Ничего между {1} и {2}
  • только |<>| между {1} и {2}
  • 0
    Если вы удаляете каждую новую строку перед началом работы с ее содержимым, как вы узнаете, где и где разрывы строк (например, разрыв строки представляет собой новую запись)?
  • 0
    задача состоит в том, чтобы удалить все переводы строки независимо от текущей ОС
Показать ещё 8 комментариев
5
$line =~ s/[\r\n]+//g;
1

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

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

1

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

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
  • 0
    Я не думаю, что chomp делает то же самое, что и другие вещи - если у вас есть файл DOS в Unix-системе, он отключит \ n с конца и оставит \ r * chomp. Эта более безопасная версия «chop» удаляет любая завершающая строка, которая соответствует текущему значению $ / (также известному как $ INPUT_RECORD_SEPARATOR в модуле «Английский»). *

Ещё вопросы

Сообщество Overcoder
Наверх
Меню