为什么我的Perl脚本会继续读取同一个文件,即使我关闭了它?

时间:2010-01-20 20:19:22

标签: perl file

我正在编写这个获取两个命令行参数的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;

换句话说,即使我知道html文件不同,$column_name在循环中的每次传递都被设置为相同的东西。

3 个答案:

答案 0 :(得分:5)

如果你使用本地词法转换为文件句柄而不是全局变量,你可能能够更快地调试它,以及打开严格的检查:

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);
  • 首先放置错误条件,因此大部分代码可以缩进一个(或多个)级别。这将提高可读性,因为当您的算法大部分在屏幕上同时显示时,您可以更好地可视化它正在做什么并捕获错误。

如果您应用以上几点我很确定您会发现您的错误。我在进行最后一次编辑时发现了它,但我想如果你自己发现它,你会学到更多。 (我不是想要傲慢;相信我!)

答案 1 :(得分:2)

您对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作为参数传递或将其填入哈希值要好得多。此外,词法文件句柄在超出范围时会自动关闭。没有机会踩到其他人已经在使用的手柄。

答案 2 :(得分:0)

您考虑过grep吗?

grep只显示包含标题的HTML行,然后处理grep的输出。

更简单,因为您不必编写任何文件处理代码。你没有用标题说出你想要的东西 - 如果你只需要一个清单,你可能根本不需要编写任何代码。

尝试类似:

grep -ri title <directoryname>