【问题标题】:Using string variables containing literal escapes in a Perl substitution在 Perl 替换中使用包含文字转义的字符串变量
【发布时间】:2014-10-08 04:59:38
【问题描述】:

我是 Perl 的新手,我发现了我不理解且无法解决的行为。

我正在制作一个小查找和替换程序,我需要做一些事情。我有一堆需要处理的文件。然后我有一个外部文本文件中的查找/替换规则列表。在替换那里我需要三个特别的东西:

  • 替换 utf-8 字符(捷克变音符号)

  • 使用添加/删除行(因此在 slurp 模式下工作)

  • 使用正则表达式

我想要一个单独运行的程序,所以我写了它,让它接受三个参数:

  • 要处理的文件
  • 发现什么
  • 替换什么。

我正在从一个解析规则列表并加载其他文件的 bash 脚本循环发送参数。

我的问题是当我在规则列表中有一个"\n" 字符串并将其发送到 Perl 脚本时。如果它在替换的第一部分(在查找部分),它会正确查找换行符,但是当它在第二部分(替换部分)时,它只会打印 \n 而不是换行符。

我尝试将 "\n" 硬编码到变量中,而不是从列表中传递它,然后它就可以正常工作了。

Perl 不在那里解释 "\n" 字符串的原因是什么,我怎样才能让它工作?

这是我的代码:

list.txt - 外部替换列表中的一行

1\. ?\\n?NÁZEV PŘÍPRAVKU;\\n<<K1>> NÁZEV PŘÍPRAVKU;

farkapitoly.sh - 用于解析 list.txt 并循环浏览所有文件并调用 Perl 脚本的 bash 脚本

...
FILE="/home/tmp.txt"
while read LINE
do
   FIND=`echo "$LINE" | awk -F $';' 'BEGIN {OFS = FS} {print $1}'`
   REPLACE=`echo "$LINE" | awk -F $';' 'BEGIN {OFS = FS} {print $2}'`
   perl -CA ./pathtiny.pl "$FILE" "$FIND" "$REPLACE" 
done < list.txt
...

pathtiny.pl - 用于查找和替换的 Perl 脚本

#!/usr/bin/perl
use strict;
use warnings;
use Modern::Perl;
use utf8; # Enable typing Unicode in Perl strings
use open qw(:std :utf8); # Enable Unicode to STDIN/OUT/ERR and filehandles

use Path::Tiny;

my $file       = path("$ARGV[0]");
my $searchStr  = "$ARGV[1]";
my $replaceStr = "$ARGV[2]";

# $replaceStr="\n<<K1>> NÁZEV PRÍPRAVKU";       # if I hardcode it here \n is replaced right away
print("Search String:",  "$searchStr",  "\n");
print("Replace String:", "$replaceStr", "\n\n");

my $guts = $file->slurp_utf8;
$guts =~ s/$searchStr/$replaceStr/gi;
$file->spew_utf8($guts);

如果它很重要,我在 VirtualBox 上使用 Linux Mint 13 64 位(在 Win 8.1 下)并且我有 Perl v5.14.2。每个文件都是带有 Linux 结尾的 UTF-8。

可以在pastebin 上找到示例文件。 this 应该像 this 一样结束。

但是示例差异很大。我需要一个通用的解决方案来在替换字符串中写下换行符,以便正确替换。

【问题讨论】:

    标签: regex perl replace


    【解决方案1】:

    您希望将文字字符串视为双引号字符串。为此,您必须翻译任何反斜杠,后跟另一个字符。

    其他专家已经向您展示了如何对整个字符串执行此操作(这是有风险的,因为它使用 eval 和未经验证的数据)。或者,您可以使用模块 String::Escape,它需要安装(不是很高,但对某些人来说太高了)。

    但是,以下以安全的方式对返回值字符串本身进行了翻译,然后可以在您的其他搜索和替换中像普通值一样使用它:

    use strict;
    use warnings;
    
    my $r = 'xx\nyy';
    
    $r =~ s/(\\.)/qq{"$1"}/eeg;  # Translate \. as a double quoted string would
    
    print $r;
    

    输出:

    xx
    yy
    

    【讨论】:

      【解决方案2】:

      问题是替换字符串是从文件中逐字读取的,所以如果你的文件包含

      xx\ny
      

      那么您将准确地阅读这六个字符。此外,替换的 replacement 部分被评估为好像它在双引号中。所以你的替换字符串是"$replaceStr",它对变量进行插值并且不再继续,所以你将再次在新字符串中拥有xx\nyy。 (顺便说一下,请避免在本地 Perl 标识符中使用大写字母,因为实际上它们是为全局变量保留的,例如 Module::Names。)

      答案在于使用eval,或者它的等价物——替换上的/e修饰符。

      如果我写

      my $str = '<b>';
      my $r = 'xx\ny';
      
      $str =~ s/b/$r/;
      

      然后替换字符串被插入到xx\ny,正如你所经历的那样。

      单个/e 修饰符将替换评估为表达式,而不仅仅是双引号字符串,但$r 作为表达式当然又是xx\ny

      您需要的是第二个/e 修饰符,它与单个/e 执行相同的评估,然后对顶部的结果进行额外的eval。为此,如果您使用qq{ .. } 是最干净的,因为您需要两个级别的引用。

      如果你写

      $str =~ s/b/qq{"$r"}/ee
      

      然后 perl 将把 qq{"$r"} 作为一个表达式求值,给出 "xx\nyy",当再次求值时,它会给你你需要的字符串 - 与表达式 'xx' . "\n" . 'yy' 相同。

      这是一个完整的程序

      use strict;
      use warnings;
      
      my $s = '<b>';
      my $r = 'xx\nyy';
      
      $s =~ s/b/qq{"$r"}/ee;
      
      print $s;
      

      输出

      <xx
      yy>
      

      但是不要忘记,如果你的替换字符串包含任何双引号,像这样

      my $r = 'xx\n"yy"'
      

      那么它们必须在通过替换之前进行转义,因为表达式本身也使用双引号。

      所有这些都很难掌握,因此您可能更喜欢 String::Escape 模块,它有一个 unbackslash 函数,它将字符串中的文字 \n (和任何其他转义)更改为其等效字符 @ 987654348@。它不是核心模块,因此您可能需要安装它。

      优点是您不再需要双重评估,因为替换字符串可以只是unbackslash $r,如果它作为表达式评估,它会给出正确的结果。它还可以毫无问题地处理 $r 中的双引号,因为表达式本身不使用双引号。

      使用String::Escape的代码是这样的

      use strict;
      use warnings;
      
      use String::Escape 'unbackslash';
      
      my $s = '<b>';
      my $r = 'xx\nyy';
      
      $s =~ s/b/unbackslash $r/e;
      
      print $s;
      

      并且输出与之前的代码相同。


      更新

      这是对使用String::Escape 的原始程序的重构。我已删除 Path::Tiny,因为我认为最好使用 Perl 的内置 inplace-edit 扩展,该扩展记录在 perlvarGeneral Variables 部分下。

      #!/usr/bin/perl
      
      use utf8;
      use strict;
      use warnings;
      use 5.010;
      use open qw/ :std :utf8 /;
      
      use String::Escape qw/ unbackslash /;
      
      our @ARGV;
      
      my ($file, $search, $replace) = @ARGV;
      
      print "Search String: $search\n";
      print "Replace String: $replace\n\n";
      
      @ARGV = ($file);
      $^I = '';
      
      while (<>) {
         s/$search/unbackslash $replace/eg;
         print;
      }
      

      【讨论】:

      • 这很好用,但我怎么可能不需要在 $searchStr 中做呢?那里应该是同样的问题,不是吗?
      • 因为 pattern 是通过双引号插值(除非您使用单引号作为分隔符),然后通过正则表达式编译器。正则表达式编译器执行与双引号相同的大部分翻译,只是它不插入变量。所以 pattern 就像将字符串通过双引号处理两次,而 replacement 字符串只处理一次,除非您使用/ee 作为修饰符。这有点混乱,但在大多数情况下,它确实符合您的意思。不幸的是,你问的这个案子特别尴尬。
      【解决方案3】:

      你得到了\n 作为字符串的内容。 (作为两个章节 1:\ 和第二个 n,而不是作为一个 newline

      \n 是文字时(例如,它在您的代码中),Perl 将其解释为换行符。

      快速解决方法是:

      my $replaceStr=eval qq("$ARGV[2]"); #evaling a string causes interpreting the \n as literal
      

      或者,如果你不喜欢 eval,你可以使用String-Escape cpan 模块。 (反斜杠函数)

      【讨论】:

        猜你喜欢
        • 2016-06-13
        • 1970-01-01
        • 2022-10-14
        • 2012-02-14
        • 2022-11-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多