web-dev-qa-db-fra.com

En Perl, comment puis-je lire un fichier entier dans une chaîne?

J'essaye d'ouvrir un fichier .html comme une grosse chaîne longue. C'est ce que j'ai

open(FILE, 'index.html') or die "Can't read file 'filename' [$!]\n";  
$document = <FILE>; 
close (FILE);  
print $document;

qui se traduit par: 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN

Cependant, je veux que le résultat ressemble à: 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

De cette façon, je peux rechercher le document entier plus facilement.

108
goddamnyouryan

Ajouter:

 local $/;

avant de lire le descripteur de fichier. Voir Comment lire simultanément un fichier entier?, ou 

perldoc perlvar et perldoc -f local .

Incidemment, si vous pouvez mettre votre script sur le serveur, vous pouvez avoir tous les modules que vous voulez. Voir Comment garder mon propre répertoire module/library?.

De plus, Path :: Class :: File vous permet de Slurp et spew .

Path :: Tiny donne des méthodes encore plus pratiques telles que Slurp, Slurp_raw, Slurp_utf8 ainsi que leurs équivalents spew .

75
Sinan Ünür

Je le ferais comme ça:

my $file = "index.html";
my $document = do {
    local $/ = undef;
    open my $fh, "<", $file
        or die "could not open $file: $!";
    <$fh>;
};

Notez l'utilisation de la version à trois arguments de open. C'est beaucoup plus sûr que les anciennes versions à deux (ou un) arguments. Notez également l'utilisation d'un descripteur de fichier lexical. Les descripteurs de fichiers lexicaux sont plus agréables que les anciennes variantes du mot à la base, pour de nombreuses raisons. Nous profitons de l’un d’eux ici: ils se ferment quand ils sont hors de portée.

93
Chas. Owens

Avec Fichier :: Slurp :

use File::Slurp;
my $text = read_file('index.html');

Oui, même vous pouvez utiliser CPAN .

74
Quentin

Tous les messages sont légèrement non idiomatiques. L'idiome est:

open my $fh, '<', $filename or die "error opening $filename: $!";
my $data = do { local $/; <$fh> };

Généralement, il n'est pas nécessaire de définir $/à undef.

48
jrockway

De perlfaq5: Comment puis-je lire un fichier entier en même temps? :


Vous pouvez utiliser le module File :: Slurp pour le faire en une seule étape.

use File::Slurp;

$all_of_it = read_file($filename); # entire file in scalar
@all_lines = read_file($filename); # one line per element

L’approche Perl habituelle pour traiter toutes les lignes d’un fichier consiste à le faire ligne par ligne:

open (INPUT, $file)     || die "can't open $file: $!";
while (<INPUT>) {
    chomp;
    # do something with $_
    }
close(INPUT)            || die "can't close $file: $!";

Cela est beaucoup plus efficace que de lire le fichier entier en mémoire sous forme de tableau de lignes, puis de le traiter un élément à la fois, ce qui est souvent, sinon presque toujours, une mauvaise approche. Chaque fois que vous voyez quelqu'un faire ceci:

@lines = <INPUT>;

vous devriez réfléchir longuement à la raison pour laquelle vous avez besoin de tout charger en même temps. Ce n'est tout simplement pas une solution évolutive. Vous pouvez également trouver plus amusant d’utiliser le module standard Tie :: File, ou les liaisons $ DB_RECNO du module DB_File, qui vous permettent de lier un tableau à un fichier afin que l’accès à un élément du tableau accède à la ligne correspondante .

Vous pouvez lire le contenu entier du fichier dans un scalaire.

{
local(*INPUT, $/);
open (INPUT, $file)     || die "can't open $file: $!";
$var = <INPUT>;
}

Cela sépare temporairement votre séparateur d’enregistrements et fermera automatiquement le fichier à la sortie du bloc. Si le fichier est déjà ouvert, utilisez simplement ceci:

$var = do { local $/; <INPUT> };

Pour les fichiers ordinaires, vous pouvez également utiliser la fonction de lecture.

read( INPUT, $var, -s INPUT );

Le troisième argument teste la taille en octets des données du descripteur de fichier INPUT et lit ce nombre en octets dans le tampon $ var.

18
brian d foy

Définissez $/ sur undef (voir la réponse de jrockway) ou concaténez toutes les lignes du fichier:

$content = join('', <$fh>);

Il est recommandé d'utiliser des scalaires pour les descripteurs de fichiers sur toutes les versions de Perl qui les prennent en charge.

7
kixx

Un moyen simple est:

while (<FILE>) { $document .= $_ }

Une autre méthode consiste à modifier le séparateur d'enregistrement d'entrée "$ /". Vous pouvez le faire localement dans un bloc nu pour éviter de changer le séparateur d’enregistrements global.

{
    open(F, "filename");
    local $/ = undef;
    $d = <F>;
}
7
user100177

Une autre façon possible:

open my $fh, '<', "filename";
read $fh, my $string, -s $fh;
close $fh;
4
echo

Vous obtenez uniquement la première ligne de l'opérateur diamant <FILE> car vous l'évaluez dans un contexte scalaire:

$document = <FILE>; 

Dans le contexte liste/tableau, l'opérateur losange retournera toutes les lignes du fichier. 

@lines = <FILE>;
print @lines;
3
Nathan

Je le ferais de la manière la plus simple, pour que tout le monde puisse comprendre ce qui se passe, même s'il existe des moyens plus intelligents:

my $text = "";
while (my $line = <FILE>) {
    $text .= $line;
}
2
open f, "test.txt"
$file = join '', <f>

<f> - retourne un tableau de lignes de notre fichier (si $/ a la valeur par défaut "\n") et que join '' y plantera ce tableau.

C’est plus une suggestion sur la façon dont PAS pour le faire. Je viens de passer un mauvais moment à trouver un bogue dans une application Perl plutôt grosse. La plupart des modules avaient leurs propres fichiers de configuration. Pour lire les fichiers de configuration dans leur ensemble, j'ai trouvé cette seule ligne de Perl quelque part sur Internet:

# Bad! Don't do that!
my $content = do{local(@ARGV,$/)=$filename;<>};

Il réaffecte le séparateur de ligne comme expliqué précédemment. Mais il réaffecte également le STDIN.

Cela a eu au moins un effet secondaire qui m'a coûté des heures à trouver: il ne ferme pas correctement le descripteur de fichier implicite (puisqu'il n'appelle pas closeat all).

Par exemple, faire cela:

use strict;
use warnings;

my $filename = 'some-file.txt';

my $content = do{local(@ARGV,$/)=$filename;<>};
my $content2 = do{local(@ARGV,$/)=$filename;<>};
my $content3 = do{local(@ARGV,$/)=$filename;<>};

print "After reading a file 3 times redirecting to STDIN: $.\n";

open (FILE, "<", $filename) or die $!;

print "After opening a file using dedicated file handle: $.\n";

while (<FILE>) {
    print "read line: $.\n";
}

print "before close: $.\n";
close FILE;
print "after close: $.\n";

résulte en:

After reading a file 3 times redirecting to STDIN: 3
After opening a file using dedicated file handle: 3
read line: 1
read line: 2
(...)
read line: 46
before close: 46
after close: 0

Ce qui est étrange, c'est que le compteur de lignes $. est augmenté d'un fichier pour chaque fichier. Il n'est pas réinitialisé et ne contient pas le nombre de lignes. Et il n'est pas remis à zéro lors de l'ouverture d'un autre fichier jusqu'à ce qu'au moins une ligne soit lue. Dans mon cas, je faisais quelque chose comme ça:

while($. < $skipLines) {<FILE>};

En raison de ce problème, la condition était fausse car le compteur de lignes n'a pas été réinitialisé correctement. Je ne sais pas s'il s'agit d'un bogue ou simplement d'un code erroné ... Appeler aussi close; ou close STDIN; n'aide pas.

J'ai remplacé ce code illisible en utilisant open, string concatenation and close. Cependant, la solution publiée par Brad Gilbert fonctionne également car elle utilise plutôt un descripteur de fichier explicite.

Les trois lignes au début peuvent être remplacées par:

my $content = do{local $/; open(my $f1, '<', $filename) or die $!; my $tmp1 = <$f1>; close $f1 or die $!; $tmp1};
my $content2 = do{local $/; open(my $f2, '<', $filename) or die $!; my $tmp2 = <$f2>; close $f2 or die $!; $tmp2};
my $content3 = do{local $/; open(my $f3, '<', $filename) or die $!; my $tmp3 = <$f3>; close $f3 or die $!; $tmp3};

qui ferme correctement le descripteur de fichier.

2
jaw

Utilisation

 $/ = undef;

avant $document = <FILE>;. $/ est le séparateur d'enregistrement input, qui est une nouvelle ligne par défaut. En redéfinissant ce paramètre sur undef, vous indiquez qu'il n'y a pas de séparateur de champ. Cela s'appelle le mode "Slurp".

D'autres solutions telles que undef $/ et local $/ (mais pas my $/) redéclare $/et produisent ainsi le même effet.

1
Geremia

Je ne sais pas si c'est une bonne pratique, mais je l'utilisais auparavant:

($a=<F>);
0
zawy

Vous pouvez simplement créer une sous-routine:

#Get File Contents
sub gfc
{
    open FC, @_[0];
    join '', <FC>;
}
0
Sheldon Juncker