【问题标题】:Read entire file then print when editing inplace?就地编辑时读取整个文件然后打印?
【发布时间】:2011-02-02 21:04:27
【问题描述】:

就地编辑的大多数示例都是单行代码,它遍历一个或多个文件,一次读取和打印一行。

我找不到任何将整个文件读入数组、根据需要修改数组、然后在使用 ^I 开关进行就地编辑时打印数组的示例。当我尝试从菱形运算符读取整个文件、编辑内容并打印整个内容时,我发现打印到 STDOUT 而不是 ARGVOUT 并且 ARGVOUT 已关闭。我可以打开相同的文件进行输出,然后打印到它,但我不确定我是否理解为什么这是必要的。这是一个例子:

#!/usr/bin/perl
use strict;
use warnings;
use 5.010;

my $filename = 'test.txt';

push @ARGV, $filename;

$^I = ".bk";

my @file = <>; #Read all records into array
chomp @file;
push @file, qw(add a few more lines);

print join "\n", @file; #This prints to STDOUT, and ARGVOUT is closed. Why?

运行上述命令会按预期备份 test.txt 文件,但会将编辑后的 ​​test.txt 保留为空,而是将编辑后的内容打印到 STDOUT。

【问题讨论】:

  • 找不到任何示例的原因是因为在 Perl 中通常认为读取整个文件而只进行逐行处理是不好的做法。 :) 有很多更好的方法来处理阅读。请参阅下面的答案,了解一些具体原因。
  • 对不起@Robert P,但有许多行处理任务最容易首先加载所有行。如果要删除文件正中间的行怎么办?删除包含 700 到 750 行之间的模式的行包含另一个模式的行之前?排序后处理输入,然后在打印前删除顶部和/或底部的一些行?
  • 很好的答案。 @mob's 和 @ephemient's 都完全按照我的意愿行事,所以真的是折腾,可以接受。
  • @mob:就像我说的,一般最好在 Perl 中逐行处理。我没有说它不能,或者根本不应该做(虽然,如果你有第二个读取文件 hanlde,你建议的前两个任务仍然可以通过逐行处理来处理!): -)

标签: perl inplace-editing


【解决方案1】:

perlrun

-i 开关被调用时,perl 使用ARGVOUT 作为默认文件句柄而不是STDOUT 来启动程序。如果有多个输入文件,则每次 &lt;&gt;&lt;ARGV&gt;readline(ARGV) 操作完成其中一个输入文件时,它会关闭 ARGVOUT 并重新打开它以写入下一个输出文件名。

一旦来自&lt;&gt; 的所有输入用尽(当没有更多文件要处理时),perl 将关闭ARGVOUT 并再次将STDOUT 恢复为默认文件句柄。或如perlrun 所说

#!/usr/bin/perl -pi.orig
s/foo/bar/;

等价于

#!/usr/bin/perl
$extension = '.orig';
LINE: while (<>) {
    if ($ARGV ne $oldargv) {
        if ($extension !~ /\*/) {
            $backup = $ARGV . $extension;
        }
        else {
            ($backup = $extension) =~ s/\*/$ARGV/g;
        }
        rename($ARGV, $backup);
        open(ARGVOUT, ">$ARGV");
        select(ARGVOUT);
        $oldargv = $ARGV;
    }
    s/foo/bar/;
}
continue {
    print;  # this prints to original filename
}
select(STDOUT);

一旦您说出my @file = &lt;&gt; 并使用所有输入,Perl 就会关闭备份文件的文件句柄并再次开始将输出定向到STDOUT


我认为,解决方法是在标量上下文中调用&lt;&gt;,并在每行之后检查eof(ARGV)。当eof(ARGV)=1 时,您已阅读该文件中的最后一行,并且在再次调用&lt;&gt; 之前有一次打印的机会:

my @file = ();
while (<>) {
    push @file, $_;
    if (eof(ARGV)) {
        # done reading current file
        @processed_file = &do_something_with(@file);
        # last chance to print before ARGVOUT gets reset
        print @processed_file;
        @file = ();
    }
}

【讨论】:

    【解决方案2】:
    my @file = <>; #Read all records into array
    

    不好。现在你已经完成了所有记录,*ARGV 已关闭,$^I 替换没有任何工作要做。

    my @file;
    while (<>) {
        push @file, $_;
    }
    continue {
        if (eof ARGV) {
            chomp @file;
            push @file, qw(add a few more lines);
            print join "\n", @file;
            @file = ();
        }
    }
    

    这会逐行读取文件,并在每个文件的末尾(在关闭之前)执行操作。

    undef $/;
    while (<>) {
        my @file = split /\n/, $_, -1;
        push @file, qw(add a few more lines);
        print join "\n", @file;
    }
    

    这会将整个文件作为单个记录一次读取。

    【讨论】:

    • continue 语句不在我的词汇表中,但现在出现了。另外,下次我想将它全部放入一个字符串并在就地编辑时进行全局替换,我会知道如何。谢谢。
    【解决方案3】:

    Tie::File 也可用于就地编辑文件。但是,它不会留下原始文件的备份副本。

    use warnings;
    use strict;
    use Tie::File;
    
    my $filename = 'test.txt';
    tie my @lines, 'Tie::File', $filename or die $!;
    push @lines, qw(add a few more lines);
    untie @lines;
    

    【讨论】:

    • 这也可以派上用场,因为当我不需要或已经有备份时。谢谢。
    【解决方案4】:

    Perl 的就地编辑比任何答案都简单:

    sub edit_in_place
    {
        my $file       = shift;
        my $code       = shift;
        {
            local @ARGV = ($file);
            local $^I   = '';
            while (<>) {
                &$code;
            }
        }
    }
    
    edit_in_place $file, sub {
        s/search/replace/;
        print;
    };
    

    如果要创建备份,请将 local $^I = ''; 更改为 local $^I = '.bak';

    【讨论】:

      猜你喜欢
      • 2020-11-30
      • 1970-01-01
      • 1970-01-01
      • 2016-08-14
      • 1970-01-01
      • 1970-01-01
      • 2015-06-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多