使用变量打开 Perl 文件

5

我试图打开一个文件夹中的文件列表。最终我想在其中插入HTML片段。到目前为止,我可以打开文件夹并读取数组中的列表。这很好。但是当我尝试使用变量作为文件名打开文件时,每次都会收到错误消息(“权限被拒绝”)。无论我是否使用$ _或将列表项的值放入变量的其他变化。我在这里找到了类似的问题,但迄今为止没有找到解决方案。以下是我的代码:

use strict;
use warnings;

my ($line);

opendir (FOLDER,"path/to/folder/./") or die "$!";
my @folderlist = readdir(FOLDER);
my @sorted_folderlist = sort @folderlist;
close(FOLDER);

foreach (@sorted_folderlist) {
  my $filename = $_;
  open (READ, ">", "path/to/folder/$filename") or die "$!";
  # do something
  close (READ);
}

这里有什么错误?我该如何使用变量作为文件名来打开文件?Pjoern,以下是我修改后用于回答问题1的代码:
my $dh; 
opendir $dh, "dir/./" or die ... 
my @folderlist = grep { -f "dir/$_" } readdir $dh; 
close $dh; 
my @sorted_folderlist = sort @folderlist; 

foreach my $filename (@sorted_folderlist) { 
  open my $fh, "<", "dir/$filename" or die ... 
  open my $writeto, ">", "new/$filename" or die ... 
  print $writeto "$fh"; 
  close $fh; 
  close $writeto; 
}

2
我已经编辑了你们两个的代码示例,添加了缩进。不用谢,但以后请自己完成它。适当的缩进是帮助人们理解你的代码的重要工具。如果你要求一大群陌生人阅读和理解你的代码,那么为他们尽可能地简单明了只是礼貌而已。 - Dave Cross
1
嗯。你编辑了你的问题并在应用@tinita的建议后展示了你的工作代码,这非常友善。我理解你想要分享并告诉我们“看我是如何让它工作的”。但现在这已经不是一个问题了。它已经包含了一个答案。人们将尝试弄清楚你的第二个代码片段有什么问题,并发现……没有任何问题。///我不确定该如何处理。有人有想法吗?@Dave? - PerlDuck
1
@PerlDuck:我认为问题仍然像之前一样清晰。第二个代码示例中仍然存在明显的问题,例如print $writeto "$fh"非常奇怪 :-) - Dave Cross
@zdim 你说得对,我得到的是 GLOB... 作为返回值。无法找到解决方案。我会查看你的答案并尝试弄清楚。 - Pjoern
@zdim 现在可以了!谢谢。你的提示最终帮了我一个大忙... - Pjoern
显示剩余5条评论
2个回答

8
您的代码存在几个问题。
首先,可能导致错误的是readdir()会返回...,但这些都是目录。因此,您试图写入directory/.
第二,您的错误信息包含$!,这很好,但是您没有输出文件名。那么您就会发现自己的错误。
第三,您调用了一个用于写入的文件句柄READ
此外,现在应该使用词法文件句柄。
use strict;
use warnings;

opendir my $dh, "dir" or die $!;
# read all files, not directories
my @folderlist = grep { -f "dir/$_" } readdir $dh;
close $dh;
my @sorted_folderlist = sort @folderlist;

foreach my $filename (@sorted_folderlist) {
    open my $fh, ">", "dir/$filename" or die "Could not write to dir/$filename: $!";
    print $fh "foo...\n";
    close $fh;
}

1
@tinita:感谢您的回答,通过您的示例,我认为我可以让我的代码工作了 - 尽管我没有理解每个细节。我认为我使用readdir()的方式是关键错误。也感谢您提供的现代化代码提示 : )。我已经在我的问题(@Dave Cross)中发布了我的代码。我正在努力改进它。 - Pjoern

6

您可以在tinita的回答中找到有关错误和良好实践的清晰解释。

我想评论一下可能的改进,即从完整路径开始构建文件列表。

  • Use map on the output of readdir to add directory to each file, then filter that

    my @files = grep { -f } map { "$dir/$_" } readdir $dh;
    

    or, implementing grep's (filtering) functionality in the map block

    my @files = map { my $fp = "$dir/$_"; -f $fp ? $fp : () } readdir $dh;
    

    Here if $fp isn't -f we return an empty list which gets flattened in output, thus vanishing.

  • Use glob, which directly gives you the full path

    use File::Glob ':bsd_glob';
    my @files = grep { -f } glob "$dir/*";
    

    where File::Glob's bsd_glob() overrides glob and handles spaces in names nicely.

注意区别:在glob中的*不会选择以点(.)开头的条目。如果你想选择这些条目,可以将其更改为glob "$dir/{*,.*}"。查看链接的File::Glob文档。

更新,以更正问题的编辑(添加)内容,也在评论中讨论过。

一旦完整路径的文件名已经读入@files中。

foreach my $file (@files) {
    my $out_file = 'new_' . $file;
    open my $writeto '>', $outfile or die "Can't open $outfile: $!";
    open my $fh,     '<', $file    or die "Can't open $file: $!";
    while (my $line = <$fn>) {
        # process $line and build what need be written to the new file
        my $new_line = ...   
        print $writeto $new_line;
    }
    close $writeto;
    close $fh;
}

(或者,如果文件名不是完整路径,请在open调用中构建完整路径。)
文件句柄在再次打开时会关闭,因此您只需要为最后一个文件显式声明close。然后,您可以在循环之前声明my($fh,$writeto);变量,在循环中使用open $fh ...(没有my),并在之后关闭文件句柄。但是这样就会使得这些变量超出了foreach范围。
文档:

glob "$dir/*" 存在注入漏洞的可能性,特别是对于非常特殊的目录名称。 虽然这只会发生在非常不寻常的名称中,但使用转义序列更安全。

my @files = grep { -f } glob "\Q$dir\E/*"

由于这个也会转义空格,因此不需要使用File::Glob中的bsd_glob()


glob是扫描目录的首选方法;File::Find也是扫描子目录的首选方法。 - shawnhcorey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接