【问题标题】:Why does my Perl script keep reading from same file, even though I closed it?为什么我的 Perl 脚本一直从同一个文件中读取,即使我关闭了它?
【发布时间】:2011-01-07 11:01:02
【问题描述】:

我正在编写这个 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 文件不同。

【问题讨论】:

  • 使用词法文件句柄。为什么要使用fileno 来检查open 是否成功?
  • 我应该如何检查成功?
  • 使用 open 调用的结果检查成功,但您应该首先阅读文档:perldoc.perl.org/functions/open.html 始终! :)

标签: perl file


【解决方案1】:

如果您将本地词法转换为文件句柄而不是全局变量,并启用严格检查,您可能会更快地调试它:

use strict;
use warnings;

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

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

这样,文件句柄将在每次循环迭代期间超出范围,因此您可以更清楚地看到具体引用的内容和位置。 (提示:您可能错过了文件句柄被关闭的条件,因此下次不正确地重用它。)

有关open 和文件句柄的最佳实践的更多信息,请参阅:

其他几点:

  • 永远不要明确分配给$_,这是自找麻烦。声明您自己的变量来保存您的数据:my $line = &lt;$filehandle&gt;(如上例所示)
  • 将匹配项直接提取到变量中,而不是使用$1$2 等,并且只对您实际需要的部分使用括号:my ($column_name) = ($line =~ m/Rank\s+Country\s+.+(\s+)Date/i);
  • 将错误条件放在首位,这样您的大部分代码就可以缩进一个(或多个)级别。这将提高可读性,因为当您的大部分算法同时显示在屏幕上时,您可以更好地可视化它正在做什么并发现错误。

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

【讨论】:

  • 感谢您的建议,我试过了,但仍然无法正常工作。有没有办法可以查看文件句柄正在使用的文件?
  • 那么,如果该行在 $line 中,我如何将匹配项直接放入 $column_name 中?
  • @Silmaril89:使用=~ 运算符:参见perldoc.perl.org/perlop.html#Binding-Operators(或perldoc.perl.org/perlre.html
  • 我听从了你的建议,老实说,我不确定我的新代码有什么不同,但现在可以了。所以,谢谢。
  • @Silmaril89:哇哦!起初,我认为问题可能出在您打开和关闭文件句柄的方式上(因此我基于此构建了我的原始回复),但我认为真正的问题在于以下几行:$_ =~ /Rank(\s+)Country(\s+)(.+)(\s+)Date/i; $column_name = $3; - 你正在抓住第三个匹配到 $column_name,但是有四组匹配的括号(要么你数错了,或者认为匹配从 0 开始计数?)
【解决方案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 作为参数传递或将其填充到哈希中会更好。此外,词法文件句柄在超出范围时会自动关闭。没有机会踩到别人已经在使用的手柄。

【讨论】:

    【解决方案3】:

    你考虑过grep吗?

    grep 从 HTML 中取出包含标题的行,然后处理 grep 的输出。

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

    尝试类似:

    grep -ri title <directoryname>
    

    【讨论】:

      猜你喜欢
      • 2015-12-01
      • 2014-11-10
      • 1970-01-01
      • 1970-01-01
      • 2022-12-05
      • 2022-12-31
      • 1970-01-01
      • 1970-01-01
      • 2017-07-12
      相关资源
      最近更新 更多