在Perl替换中使用包含文字转义的字符串变量

时间:2014-08-14 13:53:25

标签: regex perl replace

我是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一样。

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

3 个答案:

答案 0 :(得分:3)

问题是替换字符串是从文件中逐字读取的,因此如果您的文件包含

xx\ny

然后你会读到这六个字符。此外,替换的替换部分被评估为使用双引号。所以你的替换字符串是"$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(和任何其他转义)字符串到其等效字符"\n"。它不是核心模块,因此您可能需要安装它。

优点是你不再需要双重评估,因为替换字符串可以只是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 扩展程序,该扩展程序在General Variables {{3}}部分中有说明1}}。

perlvar

答案 1 :(得分:2)

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

Perl将\n解释为换行符(例如它在您的代码中)。

快速修复将是:

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

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

答案 2 :(得分:0)

您希望将文字字符串视为双引号字符串。要做到这一点,你必须翻译任何反斜杠后跟另一个角色。

其他专家已经向您展示了如何在整个字符串上执行此操作(由于它使用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