2009-09-30 7 views
2

En Perl, je lis dans les fichiers d'un répertoire, et je veux les ouvrir tous simultanément (mais ligne par ligne) afin que je puisse exécuter une fonction qui utilise toutes leurs nième lignes ensemble (par exemple concaténation).Comment ouvrir un tableau de fichiers en Perl?

my $text = `ls | grep ".txt"`; 
my @temps = split(/\n/,$text); 
my @files; 
for my $i (0..$#temps) { 
    my $file; 
    open($file,"<",$temps[$i]); 
    push(@files,$file); 
} 
my $concat; 
for my $i (0..$#files) { 
    my @blah = <$files[$i]>; 
    $concat.=$blah; 
} 
print $concat; 

Je juste un tas d'erreurs, l'utilisation de la valeur non initialisée, et les erreurs GLOB (..). Alors, comment puis-je faire ce travail?

+9

** '' Always' ** mettre use strict; utiliser les avertissements; 'au début d'un programme Perl. Faites cela jusqu'à ce que vous sachiez exactement pourquoi vous devriez faire cela. –

+4

"Faites cela jusqu'à ce que vous sachiez exactement pourquoi vous devriez faire cela." Et continuez à le faire à moins d'avoir une raison très précise de ne pas savoir exactement ce qui se passera à cause de cela. –

Répondre

8

Voici votre problème:

for my $i (0..$#files) { 
    my @blah = <$files[$i]>; 
    $concat .= $blah; 
} 

D'abord, <$files[$i]> n'est pas une lecture de descripteur de fichier valide. C'est la source de vos erreurs GLOB (...). Voir mobrule's answer pour pourquoi c'est le cas. Donc changer à ceci:

for my $file (@files) { 
    my @blah = <$file>; 
    $concat .= $blah; 
} 

Deuxième problème, vous mixez @blah (un tableau nommé blah) et $blah (un scalaire nommé blah). C'est la source de vos erreurs "non initialisées" - $blah (le scalaire) n'a pas été initialisé, mais vous l'utilisez. Si vous voulez que la ligne $n -ème de @blah, utilisez ceci:

for my $file (@files) { 
    my @blah = <$file>; 
    $concat .= $blah[$n]; 
} 

Je ne veux pas continuer de battre un cheval mort, mais je ne veux aborder une meilleure façon de faire quelque chose:

my $text = `ls | grep ".txt"`; 
my @temps = split(/\n/,$text); 

Ceci lit dans une liste de tous les fichiers dans le répertoire courant qui ont une extension ".txt" dans eux. Cela fonctionne, et est efficace, mais il peut être plutôt lent - nous devons appeler à la coquille, qui doit débourrer pour exécuter ls et grep, et cela entraîne un peu de frais généraux. En outre, ls et grep sont des programmes simples et communs, mais pas exactement portables. Certes, il y a une meilleure façon de le faire:

my @temps; 
opendir(DIRHANDLE, "."); 
while(my $file = readdir(DIRHANDLE)) { 
    push @temps, $file if $file =~ /\.txt/; 
} 

simple, court, pur Perl, pas fork, pas d'obus non-portables, et nous ne pas lire dans la chaîne et puis diviser - nous ne pouvons stocker que les entrées dont nous avons vraiment besoin. De plus, il devient trivial de modifier les conditions pour les fichiers qui passent le test. Disons que nous finissons par lire accidentellement le fichier test.txt.gz parce que notre expression régulière: nous pouvons facilement changer cette ligne à:

push @temps, $file if $file =~ /\.txt$/; 

Nous pouvons faire un avec grep (je crois), mais pourquoi se contenter de grep d » ordinaire limitée expressions quand Perl a l'une des bibliothèques regex les plus puissantes à l'intérieur?

1

utiliser des accolades autour de l'intérieur de l'opérateur $files[$i]<>

my @blah = <{$files[$i]}> 

Sinon Perl interprète <> que l'opérateur glob fichier à la place de l'opérateur de lecture-descripteur.

+0

Je savais qu'il y avait une raison «<$files[$i]>» était mauvaise. Mais ce n'est pas le seul problème dans cette boucle. –

15

Beaucoup de problèmes.En commençant par appel à « ls | grep » :)

Commençons par un code:

Tout d'abord, obtenir la liste des fichiers:

my @files = glob('*.txt'); 

Mais il serait préférable de tester si la donnée nom se rapporte à un fichier ou répertoire:

my @files = grep { -f } glob('*.txt'); 

maintenant, nous allons ouvrir ces fichiers pour les lire:

my @fhs = map { open my $fh, '<', $_; $fh } @files; 

Mais, nous avons besoin d'un moyen de gérer les erreurs - à mon avis, la meilleure façon est d'ajouter:

use autodie; 

Au début du scénario (et l'installation de autodie, si vous ne l'avez pas encore). Sinon, vous pouvez:

use Fatal qw(open); 

Maintenant que nous avons, nous allons obtenir la première ligne (comme vous montriez dans votre exemple) de toutes les entrées et concaténer:

my $concatenated = ''; 

for my $fh (@fhs) { 
    my $line = <$fh>; 
    $concatenated .= $line; 
} 

Ce qui est parfaitement bien, et facile à lire, mais peut encore être raccourci, tout en maintenant (à mon avis) la lisibilité, à:

my $concatenated = join '', map { scalar <$_> } @fhs; 

effet est le même - concaténé $ contient les premières lignes de tous les fichiers.

Ainsi, ensemble du programme ressemblerait à ceci:

#!/usr/bin/perl 
use strict; 
use warnings; 
use autodie; 
# use Fatal qw(open); # uncomment if you don't have autodie 

my @files  = grep { -f } glob('*.txt'); 
my @fhs   = map { open my $fh, '<', $_; $fh } @files; 
my $concatenated = join '', map { scalar <$_> } @fhs; 

Maintenant, il se pourrait que vous voulez concaténer non seulement les premières lignes, mais tous. Dans cette situation, au lieu du code $concatenated = ..., vous auriez besoin de quelque chose comme ceci:

my $concatenated = ''; 

while (my $fh = shift @fhs) { 
    my $line = <$fh>; 
    if (defined $line) { 
     push @fhs, $fh; 
     $concatenated .= $line; 
    } else { 
     close $fh; 
    } 
} 
+0

+1 votre code est meilleur que le mien. J'aimerais maintenir ce genre de code. Bien que pour être complet, on peut noter que 'glob()' est considéré comme une fonction peu sûre et peut ne pas fonctionner universellement. Je ne trouve pas de référence pour cela (vous pouvez rechercher StackOverflow et voir si vous pouvez trouver quelque chose à ce sujet - je me souviens d'un commentaire, mais je ne sais pas où regarder ce point). –

+0

@Chris: Hmm .. jamais entendu parler de ça, mais c'est possible. Dans ce cas - opendir, readdir + grep, closedir devrait suffire. –

+0

Je pense que les plaintes à propos de 'glob' se rapportent à des versions plus anciennes de la fonction. (Il utilisait le C-shell?) Néanmoins, voici un codeur Perl qui ne l'aime pas et pourquoi: http://sial.org/blog/2008/01/many_small_errors.html – Telemachus

1

Vous avez de bonnes réponses déjà. Une autre façon de résoudre le problème consiste à créer une liste de listes contenant toutes les lignes des fichiers (@content). Ensuite, utilisez la fonction de each_arrayrefList::MoreUtils, qui va créer un itérateur qui donne la ligne 1 de tous les fichiers, puis la ligne 2, etc.

use strict; 
use warnings; 
use List::MoreUtils qw(each_arrayref); 

my @content = 
    map { 
     open(my $fh, '<', $_) or die $!; 
     [<$fh>] 
    } 
    grep {-f} 
    glob '*.txt' 
; 
my $iterator = each_arrayref @content; 
while (my @nth_lines = $iterator->()){ 
    # Do stuff with @nth_lines; 
} 
+1

Ne serait pas 'map {[<$_>]}' travail? –

+0

@Brad - Ça pourrait, mais c'est un peu énigmatique. –

Questions connexes