Почему мой Perl-скрипт продолжает чтение из того же файла, даже если я его закрыл? - PullRequest
4 голосов
/ 20 января 2010

Я пишу этот Perl-скрипт, который получает два аргумента командной строки: каталог и год. В этом каталоге находится тонна текстовых файлов или HTML-файлов (в зависимости от года). Допустим, например, что это 2010 год, который содержит файлы, которые выглядят так: <number>rank.html с числом в диапазоне от 2001 до 2212. Я хочу, чтобы он открывал каждый файл по отдельности, занимал часть заголовка в html-файле и печатал его текстовый файл. Однако, когда я запускаю свой код, он просто печатает заголовок первого файла в текстовый файл. Кажется, что он только когда-либо открывает первый файл 2001rank.html и никаких других. Я выложу код ниже и спасибо всем, кто помогает.

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;

Другими словами, $column_name устанавливается равным одному и тому же при каждом проходе цикла, хотя я знаю, что html-файлы различаются.

Ответы [ 3 ]

5 голосов
/ 20 января 2010

Вы, вероятно, сможете отладить это намного быстрее, если вы конвертируете, используя локальные лексические выражения для файловых дескрипторов вместо глобальных, а также включите строгую проверку:

use strict;
use warnings;

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

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

Таким образом, файловые дескрипторы будут выходить из области видимости во время каждой итерации цикла, чтобы вы могли более четко видеть, на что именно ссылаются и где. (Подсказка: вы, возможно, пропустили условие, при котором дескриптор файла закрывается, поэтому он будет неправильно использован в следующий раз.)

Подробнее о передовых практиках с open и файловыми дескрипторами см .:

Некоторые другие пункты:

  • Никогда не назначайте явно $_, это вызывает проблемы. Объявите вашу собственную переменную для хранения ваших данных: my $line = <$filehandle> (как в примере выше)
  • Выводите ваши совпадения непосредственно в переменные, вместо использования $1, $2 и т. Д., И используйте скобки только для тех частей, которые вам действительно нужны: my ($column_name) = ($line =~ m/Rank\s+Country\s+.+(\s+)Date/i);
  • ставьте условия ошибки на первое место, так что основная часть вашего кода может быть перенесена на один (или более) уровень (уровни). Это улучшит читабельность, так как, когда большая часть вашего алгоритма сразу видна на экране, вы можете лучше визуализировать, что он делает, и ловить ошибки.

Если вы примените приведенные выше пункты, я почти уверен, что вы заметите вашу ошибку. Я заметил это во время последнего редактирования, но думаю, вы узнаете больше, если обнаружите это сами. (Я не пытаюсь быть надменным; поверь мне в этом!)

2 голосов
/ 21 января 2010

Ваша обработка одинакова для HTML и текстовых файлов, поэтому упростите свою жизнь и выделите общую часть:

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

Затем укажите это для двух типов ввода:

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

Тогда ваша основная программа проста:

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

Обратите внимание, как осторожно вы должны закрывать глобальные файловые дескрипторы. Пожалуйста, используйте лексические дескрипторы файлов, как в

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

Передача $fh в качестве параметра или добавление его в хэши намного приятнее. Кроме того, лексические файловые дескрипторы закрываются автоматически, когда они выходят из области видимости. Нет никакого шанса нажать на ручку, которую уже использует кто-то другой.

0 голосов
/ 20 января 2010

Рассматривали ли вы grep ?

grep только строка из HTML, содержащая заголовок, и затем обработайте вывод grep.

Проще, так как вам не нужно было бы писать код для обработки файлов. Вы не сказали, что хотите с этим заголовком - если вам нужен только список, вам может не понадобиться вообще писать код.

Попробуйте что-то вроде:

grep -ri title <directoryname>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...