【问题标题】:How is perl -i *really* implemented?perl -i *really* 是如何实现的?
【发布时间】:2015-05-29 20:52:52
【问题描述】:

http://perldoc.perl.org/perlrun.html 对 Perl -i[extension] 功能的描述中,与以下程序基本相同的代码被视为使用 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 定义的行为是在原地编辑文件,而不创建备份文件:

如果没有提供扩展,并且您的系统支持它,则原来的 当输出被重定向到一个 具有原始文件名的新文件。当 perl 退出时,无论是否干净, 原始文件已取消链接。

也许我的系统(Mac OS X Yosemite 10.10.3)不支持它。

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

在我看来,在我的系统上,如果 $ARGV$backup 具有相同的值(如果 $extension eq '' 相同,那么第 17 行上的 open(ARGVOUT, "&gt;$ARGV") 调用会在一个块之后破坏输入文件已阅读。

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

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

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

【问题讨论】:

  • 示例程序正确的原因是示例程序是关于-i.orig,而不是关于没有扩展名的-i。如果您只是删除程序中的.orig,我不知道您为什么希望示例程序的行为类似于-i。 (这就像看到5 * 5 == 25 并因此假设8 * 8 == 28。)它的正确性与“并且您的系统支持它”子句无关,这是关于您的系统是否支持保持未链接文件的打开句柄。
  • (顺便说一下,它不是-i '',而是没有参数的裸-i。字符串比较使用eq,而不是==。)
  • 虽然你可以写-i''.
  • @ruakh,我对你关于5 * 5 的观点很敏感,但这是一个应该展示 Perl 工作原理的示例,我不认为有这种感觉是不合理的这个例子有点误导。按照您的类比,就好像您告诉我,(a, b) = (5, 5) 的某些功能f(a, b) 与代码a * b“等效”,但没有提到当ab 有其他代码时代码是不同的允许值。
  • 没有。它应该显示perl -p -i.orig -e 's/foo/bar/;' 的工作原理。

标签: perl stdin


【解决方案1】:

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);

【讨论】:

  • 而 cygwin 只是悄悄默认 bare -i 表示 -i.bak(大概是为了更好地支持现有的 shell 脚本)
  • @ysth,奇怪的是取消链接技巧在 cygwin 中有效。 (不知道怎么做。)也许它以前不起作用?或者可能有一些限制?
  • 已经很久了,所以我可能记错了,但这就像通过 win32 api 在关闭标志中设置删除或在退出处理程序中实际删除它或两者兼而有之?并且可能有限制(另一个进程也打开它?)
  • @ysth,那行不通。该文件需要在关闭之前消失。我猜它会将其重命名为磁盘上的其他位置。
  • 我不认为它曾经是,参见例如permalink.gmane.org/gmane.os.cygwin/79103。但是那里引用的cygwin源文件已经不存在了,所以也许windows现在真的支持它了?
猜你喜欢
  • 1970-01-01
  • 2014-01-20
  • 1970-01-01
  • 1970-01-01
  • 2012-08-21
  • 1970-01-01
  • 2018-09-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多