在 perl 中操作反向引用以进行替换

Posted

技术标签:

【中文标题】在 perl 中操作反向引用以进行替换【英文标题】:Manipulating backreferences for substitution in perl 【发布时间】:2012-09-07 14:24:20 【问题描述】:

作为尝试用十进制数字替换科学数字的一部分,我想将反向引用保存到字符串变量中,但它不起作用。

我的输入文件是:

,8E-6,
,-11.78E-16,
,-17e+7,

然后我运行以下命令:

open FILE, "+<C:/Perl/input.txt" or die $!;
open(OUTPUT, "+>C:/Perl/output.txt") or die;

while (my $lines = <FILE>)

  $find = "(?:,)(-?)(0|[1-9][0-9]*)(\.)?([0-9]*)?([eE])([+\-]?)([0-9]+)(?:,)";
  $noofzeroesbeforecomma = eval("$7-length($4)");
  $replace = '"foo $noofzeroesbeforecomma bar"';

  $lines =~ s/$find/$replace/eeg;
  print (OUTPUT $lines);


close(FILE);

我明白了

foo  bar
foo  bar
foo  bar

我预期的地方

foo 6 bar
foo 14 bar
foo 7 bar

$noofzeroesbeforecomma 似乎为空或不存在。

即使进行了以下调整,我也得到一个空结果

$noofzeroesbeforecomma = $2;

只有在替换字符串中直接插入$2 才能给我一些东西(不幸的是,这不是我想要的)。

谁能帮忙?

我在 64 位 Windows 7 机器上运行 Strawberry Perl(5.16.1.1-64 位),并且对 Perl 非常缺乏经验

【问题讨论】:

在使用匹配或替换运算符来设置它们之前,您先使用$4$7 对不起,我误会了吗?您的第二个输入行是-11.78E-16。如果您尝试捕获指数 _sans 符号_您是否希望在靠近底部的所需输出中看到 16 而不是 14 中的 foo 14 bar 【参考方案1】:

你的主要问题是没有使用

use strict;
use warnings;

warnings 会告诉你的

Use of uninitialized value $7 in concatenation (.) or string at ...
Use of uninitialized value $4 in concatenation (.) or string at ...

我建议您尝试找到一个可以处理科学记数法的模块,而不是尝试破解您自己的模块。

您的代码在工作顺序中可能看起来像这样。如您所见,我在您的评估字符串周围放置了一个q(),以避免在$7$4 存在之前对其进行评估。我还删除了 eval 本身,因为虽然 eval 上的双重 eval 有点过分。

use strict;
use warnings;

while (my $lines = <DATA>) 
    my $find="(?:,)(-?)(0|[1-9][0-9]*)(\.)?([0-9]*)?([eE])([+\-]?)([0-9]+)(?:,)";
    my $noof = q|$7-length($4)|;
    $lines =~ s/$find/$noof/eeg;
    print $lines;



__DATA__
,8E-6,
,-11.78E-16,
,-17e+7,

输出:

6
14
7

附带说明,不使用strict 是自找麻烦。在使用诸如$noofzeroesbeforecomma 之类的变量名时这样做会带来两倍的麻烦,因为很容易出现拼写错误。

【讨论】:

我认为“不使用严格就是自找麻烦”说明了一切 @Borodin 双重麻烦。 我还建议了解正则表达式运算符上的x 标志。这使得在正则表达式中使用换行符和 cmets 成为可能,这对复杂的正则表达式有很大帮助。【参考方案2】:

这不是关于反向引用,而是原始问题,将数字从科学记数法转换。我敢肯定在某些情况下会失败:

#!/usr/bin/env perl

use strict;
use warnings;
use bignum;

for (<DATA>) 
    next unless /([+-]?\d+(?:\.\d+)?)[Ee]([+-]\d+)/;
    print $1 * 10 ** $2 . "\n";


__DATA__
,8E-6,
,-11.78E-16,
,-17e+7,

输出:

0.000008
-0.000000000000001178
-170000000

【讨论】:

【参考方案3】:

我建议您使用Regexp::Common 模块的Regexp::Common::number 插件,它会为您找到所有实数并允许您替换具有指数标记的那些

这段代码显示了这个想法。使用-keep 选项使模块将每个组件放入$N 变量之一。指数标记 - eE - 在 $7 中,因此可以根据是否存在来转换数字

use strict;
use warnings;

use Regexp::Common;

my $real_re = $REnumreal-keep;

while (<>) 
  s/$real_re/ $7 ? sprintf '%.20f', $1 : $1 /eg;
  print;

输出

根据您的示例输入,此代码生成以下内容。可以使用替换中的附加代码进一步整理这些值

,0.00000800000000000000,
,-0.00000000000000117800,
,-170000000.00000000000000000000,

【讨论】:

【参考方案4】:

问题在于 Perl 可以处理所有这些类型的表达式。由于 Perl 中的标准数据项是字符串,因此您只需 捕获 表达式即可使用它。所以,取这个表达式:

/(-?\d+(?:.\d+)?[Ee][+-]?\d+)/

从周围的文本中提取它并使用sprintf 对其进行格式化,就像 Borodin 展示的那样。

但是,如果它可以帮助您更好地了解您尝试做的事情,那么效果会更好

my ( $whole, $frac, $expon )
    = $line =~ m/(?:,)-?(0|[1-9]\d*)(?:\.(\d*))?[eE]([+\-]?\d+)(?:,)/
    ;
my $num = $expon - length( $frac );

如果您要使用它进行算术运算,为什么不使用指数无论如何 捕获符号?

最好命名您的捕获并在不需要时避开eval

替换——按原样——没有多大意义。

真的,因为符号和数字都不区分大小写,所以在开头放一个(?i),避免E“字符类”[Ee]

/((?i)-?\d+(?:.\d+)?e[+-]?\d+)/

【讨论】:

以上是关于在 perl 中操作反向引用以进行替换的主要内容,如果未能解决你的问题,请参考以下文章

perl 替换一例

如何使用 Perl 进行批量搜索和替换?

Perl 使用perl命令批量替换文件内容

Perl实例---批量替换内容

如何在保留原始字符串的同时对字符串执行 Perl 替换?

我的 perl 替换破折号模式清除了所有文件内容