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


118

J'essaye d'ouvrir un fichier .html comme une grande et longue chaîne. Voici ce que j'ai:

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

ce 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 plus facilement l'ensemble du document.


8
Vraiment devrait vérifier quelle est la définition de "Cant install", c'est un problème commun et c'est souvent un argument qui n'a pas besoin d'être fait. stackoverflow.com/questions/755168/perl-myths/…
Kent Fredric

1
Je suis en fait incapable de modifier quoi que ce soit sur tout le serveur sur lequel ce script s'exécute, à part le script lui-même.
goddamnyouryan

Vous n'êtes donc pas autorisé à ajouter des fichiers, où que ce soit sur le serveur?
Brad Gilbert

Modules FatPack dans votre script? De plus, il semble que vous envisagiez d'analyser du HTML avec des expressions régulières, non.
MkV

Réponses:


82

Ajouter:

 local $/;

avant de lire le descripteur de fichier. Voir Comment puis-je lire un fichier entier en une seule fois? , ou

$ perldoc -q "fichier entier"

Voir Variables liées aux descripteurs de fichiers dans perldoc perlvaret perldoc -f local.

Incidemment, si vous pouvez mettre votre script sur le serveur, vous pouvez avoir tous les modules que vous souhaitez. Voir Comment conserver mon propre répertoire module / bibliothèque? .

De plus, Chemin :: Class :: File vous permet de Slurp et crachez .

Chemin :: minuscule donne encore plus des méthodes pratiques telles que slurp, slurp_raw,slurp_utf8 ainsi que leurs spewhomologues.


33
Vous devriez probablement expliquer quels effets la localisation de $ / va faire ainsi que son but.
Danny

12
Si vous ne voulez rien expliquer sur la localisation $/, vous devriez probablement ajouter des liens pour plus d'informations.
Brad Gilbert

7
Une bonne explication étape par étape de ce qui est en train de faire: {local $ /; <$ fh>} est fourni ici: perlmonks.org/?node_id=287647
dawez

Peut-être dites simplement pourquoi vous devez utiliser localet non my.
Geremia

@Geremia Une discussion sur la portée est au-delà de la portée de cette réponse.
Sinan Ünür

99

Je le ferais comme ceci:

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 bareword, pour de nombreuses raisons. Nous profitons de l'un d'entre eux ici: ils se ferment lorsqu'ils sortent du champ de vision.


9
C'est probablement la meilleure façon non-cpan de le faire car il utilise à la fois l'argument 3 ouvert et maintient la variable INPUT_RECORD_SEPARATOR ($ /) localisée dans le plus petit contexte requis.
Danny

77

Avec File :: Slurp :

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

Oui, même vous pouvez utiliser CPAN .


Le PO a déclaré qu'il ne pouvait rien modifier sur le serveur. Le lien "Oui, même vous pouvez utiliser CPAN" vous montre comment contourner cette limitation, dans la plupart des cas.
Trenton

Can't locate File/Slurp.pm in @INC (@INC contains: /usr/lib/perl5/5.8/msys:(
Dmitry

2
@Dmitry - Alors installez le module. Il y a un lien d'instructions d'installation sur la page metacpan à laquelle j'ai lié à partir de cette réponse.
Quentin

53

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 $ / sur undef.


3
local $foo = undefest juste la méthode suggérée par Perl Best Practice (PBP). Si nous publions des extraits de code, je pense que faire de notre mieux pour le rendre clair serait une bonne chose.
Danny

2
Montrer aux gens comment écrire du code non idiomatique est une bonne chose? Si je voyais "local $ / = undef" dans le code sur lequel je travaillais, ma première action serait d'humilier publiquement l'auteur sur irc. (Et je ne suis généralement pas pointilleux sur les problèmes de "style".)
jrockway

1
Ok, je vais mordre: qu'est-ce qui est exactement digne d'un simulacre de "local $ / = undef"? Si votre seule réponse est «Ce n'est pas idiomatique», alors (a) je ne suis pas si sûr et (b) et alors? Je ne suis pas si sûr, car c'est terriblement courant comme moyen de faire cela. Et alors parce que c'est parfaitement clair et raisonnablement bref. Vous pouvez être plus pointilleux sur les problèmes de style que vous pensez.
Télémaque

1
La clé est que le "$ / local" fait partie d'un idiome bien connu. Si vous écrivez du code aléatoire et écrivez "local $ Foo :: Bar = undef;", c'est très bien. Mais dans ce cas très particulier, vous pourriez aussi bien parler la même langue que tout le monde, même si c'est "moins clair" (ce avec quoi je ne suis pas d'accord; le comportement de "local" est bien défini à cet égard).
jrockway

11
Désolé, pas d'accord. Il est beaucoup plus courant d'être explicite lorsque vous souhaitez modifier le comportement réel d'une variable magique; c'est une déclaration d'intention. Même la documentation utilise 'local $ / = undef' (voir perldoc.perl.org/perlsub.html#Temporary-Values-via-local () )
Leonardo Herrera

19

De perlfaq5: Comment puis-je lire un fichier entier en une seule fois? :


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 est de le faire une ligne à la fois:

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

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

@lines = <INPUT>;

vous devriez réfléchir longuement aux raisons pour lesquelles vous avez besoin de tout chargé 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 réellement à la ligne correspondante dans le fichier. .

Vous pouvez lire l'intégralité du contenu du descripteur de fichier dans un scalaire.

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

Cela annule temporairement votre séparateur d'enregistrement et ferme 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 sur le descripteur de fichier INPUT et lit autant d'octets dans le tampon $ var.


8

Un moyen simple est:

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

Une autre façon est de changer 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>;
}

1
Il y a un nombre important de problèmes avec les deux exemples que vous avez donnés. Le principal problème est qu'ils sont écrits en ancien Perl, je recommanderais de lire Modern Perl
Brad Gilbert

@Brad, le commentaire a été fait il y a des années, le point est toujours d'actualité. il vaut mieux{local $/; open(my $f, '<', 'filename'); $d = <$f>;}
Joel Berger du

@Joel ce n'est que légèrement mieux. Vous n'avez pas vérifié la sortie de openou l'appel implicitement close. my $d = do{ local $/; open(my $f, '<', 'filename') or die $!; my $tmp = <$f>; close $f or die $!; $tmp}. (Cela a toujours le problème qu'il ne spécifie pas l'encodage d'entrée.)
Brad Gilbert

use autodie, l'amélioration majeure que je voulais montrer était le descripteur de fichier lexical et les 3 arg open. Y a-t-il une raison pour laquelle vous faites docela? pourquoi ne pas simplement vider le fichier dans une variable déclarée avant le bloc?
Joel Berger du

7

Soit défini $/sur undef(voir la réponse de jrockway), soit concaténez simplement toutes les lignes du fichier:

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

Il est recommandé d'utiliser des scalaires pour les descripteurs de fichiers sur toute version de Perl qui le prend en charge.


4

Un autre moyen possible:

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

3

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

$document = <FILE>; 

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

@lines = <FILE>;
print @lines;

1
Juste un mot sur la nomenclature: l'opérateur du vaisseau spatial est <=> et le <>est l'opérateur diamant.
toolic

Oh, merci, je n'avais jamais entendu «opérateur diamant» auparavant et je pensais qu'ils partageaient tous les deux le même nom. Je vais le corriger ci-dessus.
Nathan

2

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

Toutes ces concaténations de chaînes vont être assez chères. J'éviterais de faire ça. Pourquoi déchirer les données uniquement pour les reconstituer?
andru

2
open f, "test.txt"
$file = join '', <f>

<f>- renvoie un tableau de lignes de notre fichier (si $/a la valeur par défaut "\n") et join ''collera ensuite ce tableau dans.


2

Ceci est plus une suggestion sur la façon NE PAS le faire. J'ai juste eu du mal à trouver un bogue dans une assez grosse application Perl. 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 (car il n'appelle pas closedu tout).

Par exemple, en faisant 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

La chose étrange est que le compteur de ligne $. est augmenté de un 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 ligne n'a pas été réinitialisé correctement. Je ne sais pas s'il s'agit d'un bug ou simplement d'un code erroné ... Appelant égalementclose; oder close STDIN;n'aide pas non plus.

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

Les trois lignes du 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

Utilisation

 $/ = undef;

avant $document = <FILE>;. $/est le séparateur d'enregistrement d'entrée , qui est une nouvelle ligne par défaut. En le redéfinissant pourundef , vous dites qu'il n'y a pas de séparateur de champ. C'est ce qu'on appelle le mode "slurp".

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


0

Vous pouvez simplement créer une sous-routine:

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

0

Je ne sais pas si c'est une bonne pratique, mais j'avais l'habitude d'utiliser ceci:

($a=<F>);

-1

Ce sont toutes de bonnes réponses. MAIS si vous vous sentez paresseux et que le fichier n'est pas si gros et que la sécurité n'est pas un problème (vous savez que vous n'avez pas de nom de fichier corrompu), alors vous pouvez débourser:

$x=`cat /tmp/foo`;    # note backticks, qw"cat ..." also works

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.