web-dev-qa-db-fra.com

Le moyen le plus pratique pour supprimer les sauts de ligne en Perl

Je gère un script qui peut obtenir son entrée de diverses sources et y travaille par ligne. Selon la source réelle utilisée, les sauts de ligne peuvent être de style Unix, de style Windows ou même, pour certaines entrées agrégées, mixtes (!).

Lors de la lecture d'un fichier, cela ressemble à ceci:

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

...

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

Donc, ce que je dois faire est de remplacer le chomp par quelque chose qui supprime les sauts de ligne de style Unix ou Windows. Je trouve beaucoup trop de façons de résoudre ce problème, l'un des inconvénients habituels de Perl :)

Quelle est votre opinion sur la meilleure façon de supprimer les sauts de ligne génériques? Quel serait le plus efficace?

Edit: Une petite clarification - la méthode 'process' obtient une liste de lignes quelque part, pas nécessairement lu dans un fichier. Chaque ligne peut avoir

  • Aucun saut de ligne arrière
  • Sauts de ligne de style Unix
  • Sauts de ligne de style Windows
  • Just Carriage-Return (lorsque les données d'origine ont des sauts de ligne de style Windows et sont lues avec $/= '\ n')
  • Un ensemble agrégé où les lignes ont des styles différents
50
Christoffer

Après avoir creusé un peu dans les documents perlre , je présenterai ma meilleure suggestion jusqu'à présent qui semble fonctionner assez bien. Perl 5.10 a ajouté la classe de caractères\R en tant que saut de ligne généralisé:

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

C'est la même chose que:

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

Je garderai cette question ouverte pendant un certain temps, juste pour voir s'il y a d'autres façons astucieuses qui attendent d'être suggérées.

88
Christoffer

Chaque fois que je passe par la saisie et que je souhaite supprimer ou remplacer des caractères, je le passe par de petits sous-programmes comme celui-ci.

sub clean {

    my $text = shift;

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

    return $text;
}

Ce n'est peut-être pas compliqué, mais cette méthode fonctionne parfaitement depuis des années.

12
Ted Cambron

Lecture perlport Je suggérerais quelque chose comme

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

pour être en sécurité quelle que soit la plate-forme sur laquelle vous vous trouvez et quel que soit le style de saut de ligne que vous traitez, car ce qui est dans\r et\n peut différer selon les différentes versions de Perl.

7
Olfan

Note de 2017: File :: Slurp n'est pas recommandé en raison d'erreurs de conception et d'erreurs non entretenues. Utilisez plutôt File :: Slurper ou Path :: Tiny .

étendre votre réponse

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

File :: Slurp résume le fichier IO stuff et retourne juste une chaîne pour vous.

[~ # ~] note [~ # ~]

  1. Il est important de noter l'ajout de /g, sans lui, étant donné une chaîne de plusieurs lignes, il ne remplacera que le caractère offensant d'abord.

  2. De plus, la suppression de $, qui est redondant à cet effet, car nous voulons supprimer tous les sauts de ligne, pas seulement les sauts de ligne avant ce que l'on entend par $ sur ce système d'exploitation.

  3. Dans une chaîne de plusieurs lignes, $ correspond à la fin de la chaîne et ce serait problématique).

  4. Le point 3 signifie que le point 2 part du principe que vous souhaitez également utiliser /m sinon '$' n'aurait pratiquement aucun sens pour tout ce qui est pratique dans une chaîne avec> 1 lignes, ou, en faisant un traitement sur une seule ligne, un système d'exploitation qui comprend réellement $ et parvient à trouver le \R* qui procède le $

Exemples

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

Compte tenu de la notation ci-dessus, un système d'exploitation qui ne comprend pas les délimiteurs "\ n" ou "\ r" de vos fichiers, dans le scénario par défaut avec le délimiteur par défaut du système d'exploitation défini pour $/ entraînera la lecture de votre fichier entier comme une chaîne contiguë (à moins que votre chaîne ne contienne les délimiteurs de $ OS, où elle sera délimitée par cela)

Dans ce cas, tous ces regex sont inutiles:

  • /\R*$//: N'effacera que la dernière séquence de \R dans le fichier
  • /\R*//: N'effacera que la première séquence de \R dans le fichier
  • /\012?\015?//: Quand n'effacera que le premier 012\015, \012 , ou \015 séquence, \015\012 entraînera soit \012 ou \015 en cours d'émission.

  • /\R*$//: S'il ne se trouve aucune séquence d'octets de '\ 015 $ OSDELIMITER' dans le fichier, alors [~ # ~] no [~ # ~] les sauts de ligne seront supprimés sauf pour ceux de l'OS.

Il semblerait que personne ne comprenne ce dont je parle, alors voici un exemple de code, c'est-à-dire testé à [~ # ~] pas [~ # ~] = supprimer les sauts de ligne. Exécutez-le, vous verrez qu'il laisse les sauts de ligne.

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

Pour la [~ # ~] clairement [~ # ~] sortie non traitée, voir ici: http://Pastebin.com/f2c063d74

Notez qu'il existe certaines combinaisons qui fonctionnent bien sûr, mais ce sont probablement celles que vous avez vous-même testées naïvement.

Notez que dans cette sortie, tous les résultats doivent être de la forme >|$string|<>|$string|< avec PAS D'ALIMENTS DE LIGNE pour être considéré comme une sortie valide.

et $string est de la forme générale {0}$data{1}$delimiter{2} où dans toutes les sources de sortie, il devrait y avoir:

  1. Rien entre {1} et {2}
  2. seulement |<>| entre {1} et {2}
6
Kent Fredric
$line =~ s/[\r\n]+//g;
6
dsm

Dans votre exemple, vous pouvez simplement aller:

chomp(@lines);

Ou:

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

Ou:

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

En les utilisant directement sur un fichier:

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

Perl -e 'chomp(@a=<>);print @a' <a.txt |less
2
Curtis Yallop

Pour étendre la réponse de Ted Cambron ci-dessus et quelque chose qui n'a pas été abordé ici: Si vous supprimez tous les sauts de ligne sans discernement d'un morceau de texte saisi, vous vous retrouverez avec des paragraphes qui se chevauchent sans espaces lorsque vous sortirez ce texte plus tard. Voici ce que j'utilise:

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

La dernière substitution utilise le modificateur g 'gourmand', elle continue donc de trouver des espaces doubles jusqu'à ce qu'elle les remplace tous. (Remplacer efficacement quelque chose de plus que l'espace unique)

1
freeworlder