Frage

Ich pflege ein Skript, das seine Eingaben aus verschiedenen Quellen erhalten kann, und arbeitet pro Zeile daran. Abhängig von der tatsächlichen verwendeten Quelle können Linienbreaks für einen aggregierten Eingang gemischt (!) Einen ix-Stil, Windows-Stil oder sogar für einige aggregierte Eingaben sein.

Beim Lesen aus einer Datei geht es so aus:

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

...

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

Ich muss also den Chomp durch etwas ersetzen, das entweder Unix-Stil- oder Windows-LineBreaks entfernt. Ich mache mir viel zu viele Möglichkeiten, dies zu lösen, einen der üblichen Nachteile von Perl :)

Was ist Ihre Meinung zu der ordentlichen Art, generische LineBreaks abzusetzen? Was wäre am effizientesten?

Bearbeiten: Eine kleine Klarstellung - Die Methode 'Prozess' erhält eine Liste von Zeilen von irgendwoher. Nicht mundeswehr aus einer Datei gelesen. Jede Zeile könnte haben

  • Keine nachfolgenden LineBreaks
  • LineBreaks im Unix-Stil
  • Windows-LineBreaks
  • Nur die Kutschenrückkehr (wenn Originaldaten Windows-LineBreaks enthält und mit $/ = ' n' gelesen wird)
  • Ein aggregierter Satz, bei dem Linien unterschiedliche Stile haben
War es hilfreich?

Lösung

Nach dem Graben ein bisschen durch die Perlre Dokumente ein bisschen, ich werde meinen bisher besten Vorschlag vorstellen, der ziemlich gut zu funktionieren scheint. Perl 5.10 Die R -Zeichenklasse als verallgemeinerter Linienausbruch hinzugefügt:

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

Es ist dasselbe wie:

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

Ich werde diese Frage noch eine Weile offen halten, nur um zu sehen, ob es raffinere Möglichkeiten gibt, die darauf warten, vorgeschlagen zu werden.

Andere Tipps

Wenn ich die Eingabe durchlasse und Zeichen entfernen oder ersetzen möchte, führe ich sie durch kleine Unterprogramme wie diese aus.

sub clean {

    my $text = shift;

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

    return $text;
}

Es mag nicht ausgefallen sein, aber diese Methode funktioniert seit Jahren für mich makellos.

Lektüre Perlport Ich würde so etwas wie vorschlagen

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

Um sicher zu sein, für welche Plattform, auf der Sie sich befinden, und welcher Linefeed -Stil Sie möglicherweise verarbeiten, denn was sich in r und n befindet, kann sich durch verschiedene Perl -Aromen unterscheiden.

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

Hinweis aus 2017: Datei :: Slurp wird aufgrund von Designfehlern und nicht berücksichtigten Fehlern nicht empfohlen. Verwenden Datei :: Slurper oder Pfad :: winzig stattdessen.

Erweitern Sie Ihre Antwort

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

Datei :: Slurp Abstracts das Datei -IO -Zeug weg und gibt einfach einen String für Sie zurück.

HINWEIS

  1. Wichtig, die Hinzufügung von zu beachten /g Ohne sie wird es bei einer Multi-Line-Zeichenfolge nur die ersetzt Erste Beleidigungscharakter.

  2. Auch die Entfernung von $, was für diesen Zweck überflüssig ist, wie wir uns ausziehen wollen alle Linienbrüche, nicht nur Linienbrüche vor dem, was mit dem gemeint ist $ unter diesem Betriebssystem.

  3. In einer Multi-Line-Zeichenfolge, $ entspricht dem Ende der Saite und das wäre problematisch).

  4. Punkt 3 bedeutet, dass Punkt 2 unter der Annahme hergestellt wird, dass Sie auch verwenden möchten /m Ansonsten wäre '$' im Grunde genommen bedeutungslos für alles, was in einer Zeichenfolge mit> 1 Zeilen oder ein einzelner Zeilenverarbeitung durchführt, ein Betriebssystem, das tatsächlich versteht $ und schafft es, das zu finden \R* das geht aus dem $

Beispiele

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

Angesichts der obigen Notation, ein Betriebssystem, das nicht versteht, was Ihre Dateien ' n' oder ' r' Grenzwerte haben, im Standardszenario mit dem Standardtrennzeichen des Betriebssystems für den Standard -Szenario für $/ wird dazu führen, dass Ihre gesamte Datei als eine zusammenhängende Zeichenfolge gelesen wird (es sei denn, Ihre Zeichenfolge enthält die Abgrenzer des $ -Bo -Betriebs, in dem sie dadurch abgrenzt)

In diesem Fall sind alle diese Regex nutzlos:

  • /\R*$// : Wird nur die letzte Sequenz von löschen \R in der Datei
  • /\R*// : Löscht nur die erste Sequenz von \R in der Datei
  • /\012?\015?// : Wann wird nur der erste gelöscht 012\015 , \012 , oder \015 Reihenfolge, \015\012 wird bei beiden führen \012 oder \015 emittiert werden.

  • /\R*$// : Wenn es in der Datei keine Byte -Sequenzen von ' 015 $ Osdelimiter' gibt, dann dann dann dann NEIN LineBreaks werden mit Ausnahme der eigenen des Betriebssystems entfernt.

Es scheint, dass niemand das bekommt, worüber ich spreche, also hier ist Beispielcode, das ist geprüft zu NICHT Leitungsvorschriften entfernen. Führen Sie es aus, Sie werden sehen, dass es die Linefeeds einläuft.

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

Für die DEUTLICH Unverarbeitete Ausgabe, siehe hier: http://pastebin.com/f2c063d74

Beachten Sie, dass es bestimmte Kombinationen gibt, die natürlich arbeiten, aber sie sind wahrscheinlich diejenigen, die Sie selbst getestet haben.

Beachten Sie, dass in dieser Ausgabe alle Ergebnisse der Form sein müssen >|$string|<>|$string|< mit Keine Linienfeeds als gültige Ausgabe angesehen werden.

und $string ist von der allgemeinen Form {0}$data{1}$delimiter{2} Wo in allen Ausgangsquellen, sollte es beide geben:

  1. Nichts dazwischen {1} und {2}
  2. nur |<>| zwischen {1} und {2}

In Ihrem Beispiel können Sie einfach gehen:

chomp(@lines);

Oder:

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

Oder:

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

Verwenden Sie diese direkt in einer Datei:

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

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

Um Ted Cambrons Antwort oben zu erweitern und etwas, das hier nicht angesprochen wurde: Wenn Sie alle Zeilenumbrüche wahllos aus einem Teil des eingegebenen Textes abbauen, werden Sie bei der späteren Ausgabe dieses Textes Absätze ohne Leerzeichen ineinander verlaufen, wenn Sie diesen Text ausgeben. Das benutze ich:

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;
}

Die letzte Substitution verwendet den G-Gierigen Modifikator, sodass er weiterhin Doppelräume findet, bis er alle ersetzt. (Effektiv etwas mehr als ein einzelner Raum ersetzen)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top