2010-01-20 5 views
4

J'écris ce script Perl qui obtient deux arguments de ligne de commande: un répertoire et un an. Dans ce répertoire, il y a une tonne de fichiers texte ou de fichiers html (selon l'année). Disons par exemple que c'est l'année 2010 qui contient des fichiers qui ressemblent à ceci: <number>rank.html avec le nombre allant de 2001 à 2212. Je veux qu'il ouvre chaque fichier individuellement et prenne une partie du titre dans le fichier html et l'imprime dans un texte fichier. Cependant, lorsque j'exécute mon code, il imprime simplement le premier titre de fichier dans le fichier texte. Il semble qu'il ouvre seulement le premier fichier 2001rank.html et pas d'autres. Je vais poster le code ci-dessous et merci à tous ceux qui aident.Pourquoi mon script Perl continue de lire le même fichier, même si je l'ai fermé?

my $directory = shift or "Must supply directory\n"; 
my $year = shift or "Must supply year\n"; 

unless (-d $directory) { 
    die "Error: Directory must be a directory\n"; 
} 

unless ($directory =~ m/\/$/) { 
    $directory = "$directory/"; 
} 

open COLUMNS, "> columns$year.txt" or die "Can't open columns file"; 
my $column_name; 

for (my $i = 2001; $i <= 2212; $i++) { 

    if ($year >= 2009) { 
    my $html_file = $directory.$i."rank.html"; 
    open FILE, $html_file; 

    #check if opened correctly, if not, skip it 
    unless (defined fileno(FILE)) { 
     print "skipping $html_file\n"; 
     next; 
    } 

    $/ = "\n"; 
    my $line = <FILE>; 

    if (defined $line) { 
     $column_name = ""; 
     $_ = <FILE> until m{</title>}; 
     $_ =~ m{<title>CIA - The World Factbook -- Country Comparison :: (.+)</title>}i; 
     $column_name = $1; 
    } 
    else { 
     close FILE; 
     next; 
    } 
    close FILE; 
    } 
    else { 
    my $text_file = $directory.$i."rank.txt"; 
    open FILE, $text_file; 

    unless (defined fileno(FILE)) { 
     print "skipping $text_file\n"; 
     next; 
    } 

    $/ = "\r"; 
    my $line = <FILE>; 

    if (defined $line) { 
     $column_name = ""; 
     $_ = <FILE> until /Rank/i; 
     $_ =~ /Rank(\s+)Country(\s+)(.+)(\s+)Date/i; 
     $column_name = $3; 
    } 
    else { 
     close FILE; 
     next; 
    } 
    close FILE; 
    } 

    print "Adding $column_name to text file\n"; 
    print COLUMNS "$column_name\n"; 
} 

close COLUMNS; 

En d'autres termes $column_name obtient égal à la même chose chaque passage dans la boucle, même si je sais que les fichiers html sont différents.

+2

Utilisez des handles de fichiers lexicaux. Pourquoi vérifiez-vous le succès de 'open' en utilisant' fileno'? –

+0

Comment devrais-je vérifier le succès? – adhanlon

+2

Vérifiez le succès avec le résultat de l'appel ouvert, mais vous devriez vraiment lire le doc en premier: http://perldoc.perl.org/functions/open.html Toujours! :) – sebthebert

Répondre

5

Vous serez probablement en mesure de déboguer ce beaucoup plus rapidement si vous convertir à l'aide lexicales local pour votre lieu de handles de fichiers globals, ainsi que d'activer la vérification stricte:

use strict; 
use warnings; 

while (...) 
{ 
    # ... 
    open my $filehandle, $html_file; 

    # ... 
    my $line = <$filehandle>; 
} 

De cette façon, le descripteur de fichier (s) sera hors de portée lors de chaque itération de boucle, de sorte que vous pouvez voir plus clairement ce qui est exactement référencé et où. (Conseil:. Vous avez manqué une condition où le descripteur de fichier se ferme, il est donc mal réutilisé la prochaine fois)

Pour en savoir plus sur les meilleures pratiques open et handles de fichiers, voir:

Quelques autres points:

  • Ne jamais attribuer explicitement à $_, cela demande des problèmes. Déclarez votre propre variable pour stocker vos données: my $line = <$filehandle> (comme dans l'exemple ci-dessus)
  • Tirez vos résultats directement dans les variables, plutôt que d'utiliser $1, $2 etc, et utiliser uniquement entre parenthèses pour les portions que vous avez réellement besoin: my ($column_name) = ($line =~ m/Rank\s+Country\s+.+(\s+)Date/i);
  • mettre les conditions d'erreur en premier, donc la majeure partie de votre code peut être dépassée d'un (ou plusieurs) niveau (s). Cela améliorera la lisibilité, car lorsque la majeure partie de votre algorithme est visible à la fois sur l'écran, vous pouvez mieux visualiser ce qu'il fait et détecter les erreurs.

Si vous appliquez les points ci-dessus, je suis sûr que vous détecterez votre erreur. Je l'ai repéré en faisant ce dernier montage, mais je pense que vous en apprendrez plus si vous le découvrez vous-même. (Je n'essaie pas d'être arrogant, croyez-moi sur ce point!)

+0

Merci pour la suggestion, j'ai essayé, mais ça ne marche toujours pas bien. Est-il possible de voir quel fichier utilise un descripteur de fichier? – adhanlon

+0

@ Silmaril89: voir ma dernière édition. – Ether

+0

Alors, comment pourrais-je obtenir la correspondance directement dans $ nom_colonne si la ligne est dans la ligne $? – adhanlon

0

Avez-vous considéré grep?

grep sortir juste la ligne du code HTML contenant le titre, puis traiter la sortie de grep.

Plus simple, car vous n'aurez à écrire aucun code de gestion de fichiers. Vous n'avez pas dit ce que vous voulez avec ce titre - si vous avez seulement besoin d'une liste, vous n'aurez peut-être pas besoin d'écrire du code du tout.

Essayez quelque chose comme:

grep -ri title <directoryname> 
2

Votre traitement est similaire pour les fichiers HTML et texte, afin de rendre votre vie facile et factoriser la partie commune:

sub scrape { 
    my($path,$pattern,$sep) = @_; 

    unless (open FILE, $path) { 
    warn "$0: skipping $path: $!\n"; 
    return; 
    } 

    local $/ = $sep; 

    my $column_name; 
    while (<FILE>) { 
    next unless /$pattern/; 
    $column_name = $1; 
    last; 
    } 

    close FILE; 

    ($path,$column_name); 
} 

ensuite le rendre spécifique pour la deux types d'entrée:

sub scrape_html { 
    my($directory,$i) = @_; 

    scrape $directory.$i."rank.html", 
     qr{<title>CIA - The World Factbook -- Country Comparison :: (.+)</title>}i, 
     "\n"; 
} 

sub scrape_txt { 
    my($directory,$i) = @_; 

    scrape $directory.$i."rank.txt", 
     qr/Rank\s+Country\s+(.+)\s+Date/i, 
     "\r"; 
} 

Ensuite, votre programme principal est simple:

my $directory = shift or die "$0: must supply directory\n"; 
my $year  = shift or die "$0: must supply year\n"; 

die "$0: $directory is not a directory\n" 
    unless -d $directory; 

# add trailing slash if necessary 
$directory =~ s{([^/])$}{$1/}; 

my $columns_file = "columns$year.txt"; 
open COLUMNS, ">", $columns_file 
    or die "$0: open $columns_file: $!"; 

for (my $i = 2001; $i <= 2212; $i++) { 
    my $process = $year >= 2009 ? \&scrape_html : \&scrape_txt; 

    my($path,$column_name) = $process->($directory,$i); 

    next unless defined $path; 

    if (defined $column_name) { 
    print "$0: Adding $column_name to text file\n"; 
    print COLUMNS "$column_name\n"; 
    } 
    else { 
    warn "$0: no column name in $path\n"; 
    } 
} 

close COLUMNS or warn "$0: close $columns_file: $!\n"; 

Notez à quel point vous devez être prudent pour fermer les handles de fichiers globaux. S'il vous plaît utiliser lexicales comme dans les handles de fichiers

open my $fh, $path or die "$0: open $path: $!"; 

En passant $fh comme paramètre ou la farce dans hash est beaucoup plus agréable. En outre, les handles de fichiers lexicaux se ferment automatiquement lorsqu'ils sortent du cadre. Il n'y a aucune chance de piétiner sur une poignée que quelqu'un d'autre utilise déjà.

Questions connexes