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

ato*_*c44 5 regex perl replace

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

\n\n

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

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

  • \n
  • 添加/删除行(因此在吸食模式下工作)

  • \n
  • 使用正则表达式

  • \n
\n\n

我想要一个单独运行的程序,所以我编写了它,以便它需要三个参数:

\n\n
    \n
  • 要处理的文件
  • \n
  • 寻找什么
  • \n
  • 换什么。
  • \n
\n\n

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

\n\n

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

\n\n

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

\n\n

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

\n\n

这是我的代码:

\n\n

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

\n\n
1\\. ?\\\\n?N\xc3\x81ZEV P\xc5\x98\xc3\x8dPRAVKU;\\\\n<<K1>> N\xc3\x81ZEV P\xc5\x98\xc3\x8dPRAVKU;\n
Run Code Online (Sandbox Code Playgroud)\n\n

farkapitoly.shlist.txt - 用于解析和循环所有文件并调用 Perl 脚本的bash 脚本

\n\n
...\nFILE="/home/tmp.txt"\nwhile read LINE\ndo\n   FIND=`echo "$LINE" | awk -F $\';\' \'BEGIN {OFS = FS} {print $1}\'`\n   REPLACE=`echo "$LINE" | awk -F $\';\' \'BEGIN {OFS = FS} {print $2}\'`\n   perl -CA ./pathtiny.pl "$FILE" "$FIND" "$REPLACE" \ndone < list.txt\n...\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
#!/usr/bin/perl\nuse strict;\nuse warnings;\nuse Modern::Perl;\nuse utf8; # Enable typing Unicode in Perl strings\nuse open qw(:std :utf8); # Enable Unicode to STDIN/OUT/ERR and filehandles\n\nuse Path::Tiny;\n\nmy $file       = path("$ARGV[0]");\nmy $searchStr  = "$ARGV[1]";\nmy $replaceStr = "$ARGV[2]";\n\n# $replaceStr="\\n<<K1>> N\xc3\x81ZEV PR\xc3\x8dPRAVKU";       # if I hardcode it here \\n is replaced right away\nprint("Search String:",  "$searchStr",  "\\n");\nprint("Replace String:", "$replaceStr", "\\n\\n");\n\nmy $guts = $file->slurp_utf8;\n$guts =~ s/$searchStr/$replaceStr/gi;\n$file->spew_utf8($guts);\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

示例文件可以在 上找到pastebin应该是这样的。

\n\n

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

\n

Bor*_*din 3

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

xx\ny
Run Code Online (Sandbox Code Playgroud)

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

答案在于使用eval或其等效项 -/e替换修饰符。

如果我写

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

$str =~ s/b/$r/;
Run Code Online (Sandbox Code Playgroud)

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

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

您需要的是第二个/e修改器,它执行与单个修改器相同的评估/e,然后eval在顶部执行附加结果。为此,如果您qq{ .. }需要两级报价,那么使用它是最干净的。

如果你写

$str =~ s/b/qq{"$r"}/ee
Run Code Online (Sandbox Code Playgroud)

然后 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;
Run Code Online (Sandbox Code Playgroud)

输出

<xx
yy>
Run Code Online (Sandbox Code Playgroud)

但不要忘记,如果您的替换字符串包含任何双引号,如下所示

my $r = 'xx\n"yy"'
Run Code Online (Sandbox Code Playgroud)

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

所有这些都很难掌握,因此您可能更喜欢该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;
Run Code Online (Sandbox Code Playgroud)

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


更新

这是使用 的原始程序的重构String::Escape。我已经删除了,Path::Tiny因为我认为最好使用 Perl 的内置就地编辑扩展,该扩展记录在perlvar.

#!/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;
}
Run Code Online (Sandbox Code Playgroud)