perl -i *真正* 是如何实现的?

How is perl -i *really* implemented?

http://perldoc.perl.org/perlrun.html 处对 Perl -i[extension] 功能的描述中,与以下程序实质上相同的代码被指定为 "an equivalent" 使用 perl -pi.orig ...

#!/usr/bin/perl

use strict;
use warnings;

my $extension = '.orig';
my $oldargv = '';
my $backup;
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;
    }
    # Don't change anything; just copy.
}
continue {
    print;
}
select(STDOUT);

这在 $extension eq '.orig' 时工作正常;然而,Perl 也定义了没有扩展名的 -i(即 $extension eq '')。 Perl 定义的行为是就地编辑文件,不创建备份文件:

If no extension is supplied, and your system supports it, the original file is kept open without a name while the output is redirected to a new file with the original filename. When perl exits, cleanly or not, the original file is unlinked.

可能是我的系统(Mac OS X Yosemite 10.10.3)不支持。

如果我在这个程序中设置 $extension = '',那么对于小于一个 STDIN 块的文件(在 AcivePerl 5.10 中为 4096 字节,但在 ActivePerl 5.16 中为 8192 字节),代码将正常工作,但它会 适用于大于一个块的文件。

在我看来,在我的系统上,如果 $ARGV$backup 具有相同的值(如果 $extension eq '',它们将具有相同的值,那么 open(ARGVOUT, ">$ARGV") 调用第 17 行在读取输入文件的一个块后破坏了输入文件。

当然,我可以解决这个问题,方法是写入一个临时文件,然后在最后重命名它。但我有点失望,经过几个小时的调试,perlrun 中的示例并不像我预期的那样通用。

  1. 是否有处理 $extension eq '' 案例的标准惯用方法?

  2. 这个 $extension eq '' 用例是否足够重要以至于应该编辑 perlrun?当然,"and your system supports it" 子句意味着这个例子并没有错,但是如果这个例子也涵盖了这种情况就会更有用。

Perl 5.28 changed -i。此答案适用于早期版本的 Perl。


提供扩展时:

open(my $fh_in,  '<', $qfn);
rename($qfn, "$qfn$ext");
open(my $fh_out, '>', $qfn);

这可以用strace看到。

$ strace perl -i~ -pe1 a
...
open("a", O_RDONLY)                     = 3
rename("a", "a~")                       = 0
open("a", O_WRONLY|O_CREAT|O_EXCL, 0600) = 4
...

未提供扩展名时:

open(my $fh_in,  '<', $qfn);
unlink($qfn);
open(my $fh_out, '>', $qfn);

这可以用strace看到。

$ strace perl -i -pe1 a
...
open("a", O_RDONLY)                     = 3
unlink("a")                             = 0
open("a", O_WRONLY|O_CREAT|O_EXCL, 0600) = 4
...

Mac 等Unix 系统支持匿名文件。 Windows 没有,所以 -i 需要在那里扩展。

>perl -i.bak -pe1 a

>perl -i -pe1 a
Can't do inplace edit without backup.

如果我们将这些知识整合到您发布的代码中,我们将得到以下结果:

#!/usr/bin/perl

use strict;
use warnings;

my $extension = '.orig';
my $oldargv = '';
my $backup;
LINE: while (<>) {
    if ($ARGV ne $oldargv) {
        if (length($extension)) {
            if ($extension !~ /\*/) {
                $backup = $ARGV . $extension;
            } else {
                ($backup = $extension) =~ s/\*/$ARGV/g;
            }
            rename($ARGV, $backup);
        } else {
            die("Can't do inplace edit without backup.\n") if $^O eq 'MSWin32';
            unlink($ARGV);
        }
        open(ARGVOUT, ">$ARGV");
        select(ARGVOUT);
        $oldargv = $ARGV;
    }
    # Don't change anything; just copy.
}
continue {
    print;
}
select(STDOUT);