2009-05-19 16 views
48

Sto mantenendo uno script che può ottenere il suo input da varie fonti e lavora su di esso per riga. A seconda della sorgente effettiva utilizzata, le interruzioni di riga potrebbero essere in stile Unix, stile Windows o pari, per alcuni input aggregati, misti (!).Il modo migliore per rimuovere interruzioni di riga in Perl

Durante la lettura da un file va qualcosa di simile:

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

... 

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

Allora, che cosa devo fare è sostituire il chomp con qualcosa che libera in Unix-stile o interruzioni di riga stile Windows. Mi vengono in mente troppi modi per risolvere questo problema, uno dei soliti inconvenienti di Perl :)

Qual è la tua opinione sul modo più accurato per eliminare i linebreak generici? Quale sarebbe il più efficiente?

Modifica: un piccolo chiarimento: il metodo "processo" ottiene un elenco di linee da qualche parte, non letto in lettura da un file. Ogni linea potrebbe avere

  • Nessun linebreaks finali
  • interruzione di linea in stile Unix
  • linebreaks stile Windows
  • Proprio ritorno a capo (quando i dati originali ha interruzioni di linea in stile Windows e viene letto con $/= '\ n')
  • Un set aggregato in cui le linee hanno stili differenti
+0

Se l'operatore <> riconosce le nuove linee, non morde? – outis

+0

Ma l'operatore <> non riconosce correttamente le newline e oltre all'utilizzo di <> è un caso speciale, l'input non viene sempre da un file. – Christoffer

+1

o esegue il codice che ho appena incollato o letto l'output allegato che genera. Spero che vedrai il punto che sto cercando di fare. La condizione "mista" è di gran lunga la peggiore. –

risposta

82

Dopo aver scavato un po 'tra i documenti perlre, presenterò il mio suggerimento migliore finora che sembra funzionare piuttosto bene. Perl 5.10 ha aggiunto la classe di caratteri \ R come un'interruzione di linea generalizzato:

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

E 'lo stesso di:

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

Terrò questa domanda aprire un po' ancora, solo per vedere se c'è di più elegante modi in attesa di essere suggerito.

+1

, ti incoraggio ad accettare la tua risposta, se funziona per te. \ R potrebbe non funzionare come previsto su alcune piattaforme esotiche (motivo per cui ho suggerito l'approccio cablato in precedenza), ma se non stai scrivendo codice portatile ma vuoi solo portare a termine il lavoro, hai finito qui. Si potrebbe prendere in considerazione l'idea di inserire i file di test di Kent Fredric attraverso il proprio codice perché sono davvero un buon banco di prova. – Olfan

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

lettura perlport Io suggerirei qualcosa come

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

per essere sicuro per qualsiasi piattaforma sei su e qualunque sia lo stile di avanzamento riga si può essere l'elaborazione perché ciò che è in \ r \ n possono differire attraverso diversi sapori Perl.

+1

Potenziali bug: 1) No/g, quindi non funzionerà stringhe multi-linea. 2) $, quindi corrisponderà solo ai delimitatori che si verificano direttamente prima della fine della stringa. 3) corretto \ 015 \ 012 ordine, in modo che se hanno \ 012 \ 015 ne mangerà solo uno. –

+2

1) +2) Poiché non so cosa ci sia all'interno delle righe, ho dovuto supporre che all'interno ci possano essere caratteri di nuova riga che non dovrebbero essere rimossi (ad es.record di database con colonne di dati linebreaking). La mia intenzione era di abbinare il comportamento di chomp() il più vicino possibile. 3) Ho visto i vecchi Mac usare solo \ 015 e Windows usa ancora \ 015 \ 012, ma devo ancora vedere un sistema del mondo reale usando \ 012 \ 015, quindi ho sentito che questo ordine sarebbe stato sicuro. ;) – Olfan

+0

Dai un'occhiata alla mia risposta aggiornata e a ciò che emette, ci sono condizioni * specialmente * prevalenti nella lettura basata su linee che in realtà non sono ovvie finché non la provi. es.: local $/= "\ 015" # all'improvviso ci sono molti \ 012 in uscita. –

6

Nota a partire dal 2017: File :: Slurp non è consigliato a causa di errori di progettazione e errori non mantenuti. Utilizzare invece File::Slurper o Path::Tiny.

si estende sul vostra risposta

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

abstracts File :: Slurp via la roba IO file e solo restituisce una stringa per voi.

NOTA

  1. Importante notare l'aggiunta di /g, senza di essa, data una stringa multi-linea, che andrà a sostituire solo il carattere incriminato prima.

  2. Inoltre, la rimozione di $, che è ridondante per questo scopo, come noi vogliamo mettere a nudo tutte le interruzioni di riga, non solo interruzioni di linea prima di tutto ciò che si intende con $ su questo sistema operativo.

  3. In una stringa multi linea, $ corrisponde alla fine della stringae che sarebbe problematico). Punto 3 significa che il punto 2 è fatto con l'ipotesi che si desideri utilizzare anche /m altrimenti '$' sarebbe praticamente privo di significato per qualsiasi cosa utile in una stringa con> 1 righe, oppure, eseguendo elaborazione a riga singola , un sistema operativo che in realtà capisce $ e riesce a trovare il \R* che procedono il $

Esempi

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

Data la notazione di cui sopra, un sistema operativo che non comprende i delimitatori dei file \ n 'o' \ r ', nello scenario predefinito con il delimitatore predefinito del sistema operativo impostato per $/ provocherà la lettura dell'intero file come una stringa contigua (a meno che la stringa ha delimitatori del $ OS in esso, dove sarà delimitare con questo)

Quindi, in questo caso, tutte queste espressioni regolari sono inutili:

  • /\R*$//: servirà solo a cancellare l'ultima sequenza di \R nel file
  • /\R*//: Solo e rase la prima sequenza di \R nel file
  • /\012?\015?//: Quando si cancella solo la prima 012\015, \012, o sequenza \015, \015\012 si traduca in \012 o \015 emessa.

  • /\R*$//: Se vi capita di essere non sequenze di byte di '\ 015 $ OSDELIMITER' nel file, quindi poi NO interruzione di linea saranno rimossi se non per quelle proprie della OS.

Sembrerebbe nessuno ottiene ciò di cui sto parlando, ecco esempio di codice, cioè testati-NON linea di rimuovere feed. Eseguilo, vedrai che lascia i linefeed dentro.

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

Per l'uscita Lordo CHIARAMENTE, vedere qui: http://pastebin.com/f2c063d74

Nota ci sono alcune combinazioni che di lavoro del corso, ma sono probabilmente quelli che da soli ingenuamente alla prova.

Si noti che in questa uscita, tutti i risultati devono essere del modulo >|$string|<>|$string|< con NO LINE FEED da considerare uscita valida.

e $string è la forma generale {0}$data{1}$delimiter{2} dove in tutte le fonti di uscita, ci dovrebbe essere:

  1. nulla tra {1} e {2}
  2. solo |<>| tra {1} e {2}
+0

Se si elimina * ogni * new-line prima di lavorare sul suo contenuto, come si fa a sapere dove si interrompe la linea (ad esempio se un'interruzione di riga costituisce un nuovo record)? – Anon

+0

l'operazione è di rimuovere * all * linefeed indipendentemente dal sistema operativo attuale –

+0

No, l'attività è di rimuovere i linefeed finali da un elenco di stringhe. – Christoffer

11

Ogni volta Vado attraverso l'input e voglio rimuovere o sostituire i caratteri lo eseguo attraverso piccole subroutine come questa .

sub clean { 

    my $text = shift; 

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

    return $text; 
} 

Potrebbe non essere di fantasia, ma questo metodo ha funzionato in modo impeccabile per me per anni.

+0

Scommetto che questa soluzione è probabilmente più efficiente di una regex condizionale. Buona risposta. – freeworlder

1

Nel tuo esempio, si può solo andare:

chomp(@lines); 

Oppure:

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

Oppure:

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

L'utilizzo di questi direttamente su un file:

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

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

Non penso che chomp faccia la stessa cosa delle altre cose - se hai un file dos su un sistema unix, prenderà \ n alla fine e lascerà il \ r * chomp Questa versione più sicura di "chop" rimuove qualsiasi stringa finale corrispondente al valore corrente di $/(noto anche come $ INPUT_RECORD_SEPARATOR nel modulo "English"). * – msouth

1

Per estendere la risposta di Ted Cambron in alto e qualcosa che non è stato risolto qui: Se rimuovi tutte le interruzioni di riga indiscriminatamente da un blocco di testo immesso, finirai con i paragrafi che si incontrano l'uno nell'altro senza spazi quando scrivi quel testo in un secondo momento . Questo è quello che uso:

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

L'ultima sostituzione utilizza il modificatore di g 'avidi' in modo che continua a trovare doppi spazi fino a quando non li sostituisce. (Sostituendo efficacemente qualsiasi altro spazio singolo)

Problemi correlati