【问题标题】:Using perl one-liner in perl script在 perl 脚本中使用 perl one-liner
【发布时间】:2016-11-22 08:57:48
【问题描述】:

我在一个 perl 脚本中嵌入了一个 perl one-liner,其目的是为扩展名为 .ini 的文件的每一行就地替换一个字符串,但我发现每个文件都有备份使用 .bak。

由于在单行中,我只指定-i而不是-i.bak,不知道为什么会生成.bak文件。

foreach my $dir (glob "*.ini") {
    system qq(perl -i -pe 's|Default|UTF-8|' $dir);          
}

另一个问题是是否有更好、更简洁的 perl 脚本来实现相同的目标(无需备份 .bak 文件),无论是否单行。

【问题讨论】:

  • 您在使用 Windows 系统吗?
  • @Borodin,是的,Windows 中有什么意外吗?

标签: perl in-place


【解决方案1】:

我想您可能正在查看一些旧数据文件。据我所知,Perl 在任何情况下都不会假定默认文件扩展名为 .bak

如果您仅将选项指定为-i,则它将尝试通过在打开输入文件后删除输入文件并创建具有相同名称的新输出文件来就地编辑文件。旧文件在关闭之前仍然可读

此技术不适用于 Windows,因此在该平台上使用裸 -i 选项是错误的

没有必要为了编辑一些文件而使用第二个 perl 进程。可以像这样非常简单地完成。内部变量$^I 对应于命令行中-i 选项的值。再说一次,如果您使用的是 Windows 系统,那么 $^I 可能不是空字符串,您需要将其设置为 .bak 或类似的东西

{
    local @ARGV = glob 'f.*';
    local $^I = '';  # Must be non-blank on Windows

    while ( <> ) {
        s/Default/UTF-8/g;
        print;
    }
}

【讨论】:

    【解决方案2】:

    perl -i 归结为以下核心:

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

    $fh_in 称为匿名文件句柄。注意它从中读取的关联文件如何不再存在于目录中。

    问题在于 Windows 不支持匿名文件句柄。当您删除已打开的文件时,目录条目会一直保留到文件关闭为止。这可以防止 Perl 创建具有相同名称的输出文件。这就是 Windows 不支持 -i(没有扩展名)的原因。

    >perl -i -ne"..." file
    Can't do inplace edit without backup.
    

    但是您没有使用 Windows。您正在使用 cygwin,一个模拟 unix 的环境。这就是为什么使用-i(没有扩展名)不会给你一个错误。但是,cygwin 仍然受限于主机操作系统的能力,所以它不能创建匿名文件[1]。因此,Perl 被编程为当 -i 被提供给 Perl 的 cygwin 构建时,就像提供了 -i.bak 一样。


    1. 它似乎能够模仿它们,但由于某种原因没有使用该功能。

      $ cat foo
      abc
      def
      
      $ perl -E'
         open(my $fh, "<", "foo") or die $!;
         system("ls -1");
         say "--";
         unlink("foo") or warn $!;
         system("ls -1");
         say "--";
         print while <$fh>;
      '
      foo
      --
      --
      abc
      def
      

    【讨论】:

      【解决方案3】:

      这里有一种方法可以做到这一点,而不必编写另一个 perl 实例。此技术使用中间临时文件:

      use warnings;
      use strict;
      
      for my $file (glob "*.ini") {
          open my $fh, '<', $file or die $!;
          open my $wfh, '>', 'temp.txt' or die $!;
      
          while (<$fh>){
              s/Default/UTF-8/;
              print $wfh $_;
          }
          close $fh;
          close $wfh;
          unlink $file or die $!;
          rename 'temp.txt', $file;
      }
      

      如果您知道自己的文件不会太大,请使用此版本。它将整个文件读入内存(单个字符串标量),并且不需要中间临时文件:

      use warnings;
      use strict;
      
      for my $file (glob "*.ini") {
          open my $fh, '<', $file or die $!;
      
          my $contents;
          {
              local $/;
              $contents = <$fh>;
          }
          close $fh;
      
          $contents =~ s/Default/UTF-8/;
      
          open my $wfh, '>', $file or die $!;
          print $wfh $contents;
          close $wfh;
      }
      

      【讨论】:

      • 第一个示例中的 rename 将失败,因为具有该名称的原始文件仍然存在。
      • 在 Linux Mint 上对我来说不会失败。你的意思是在 Windows 上?
      • 是的。我们发现 OP 运行的是 Windows 系统。
      • 谢谢@Borodin;我已将其更新为对 Windows 友好
      • 这与-i.bak 所做的几乎相同。
      【解决方案4】:

      我认为 bash 命令 find + xargs 和 perl 应该比 perl 循环更容易。

      find . -name '*.ini' |xargs perl -pi -e 's/Default/UTF-8/g'
      

      【讨论】:

        猜你喜欢
        • 2014-07-31
        • 2014-03-19
        • 2014-05-08
        • 2013-09-02
        • 2018-05-08
        • 2012-08-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多