Dan
Dan

Reputation: 157

How to use Perl to perform substitutions based on calculations?

I'm trying to write a perl script to search through a text file, find all decimal numbers, and modify them by some scaling factor. So far, I have succeeded in extracting the numbers with regular expressions:

open(INPUT, $inputPath) or die "$inputPath cannot be opened.";

while ($thisLine = <INPUT>) {
  while ($thisLine =~ m/(-*\d+\.\d+)/g) {
    if(defined($1)) { 
      $new = $scalingFactor*$1;
      print $new."\n";
    }
  }
}

close (INPUT);

However, I haven't yet figured out how to reinsert the new values into the file. I tried using s/(-*\d+.\d+)/$scalingFactor*$1/g for substitution, but of course this inserted the string representation of $scalingFactor instead of evaluating the expression.

I'm a perl newbie, so any help would be greatly appreciated. Thanks in advance,

-Dan

Edit: Solution (based on Roman's Reply)

while ($thisLine = <INPUT>) {
  $thisLine =~ s/(-*\d+\.\d+)/$scalingFactor*$1/ge;
  prinf OUTPUT $thisLine;
}

Alternatively, Sean's solution also worked great for me. Thanks all!

Upvotes: 3

Views: 1869

Answers (2)

Sean
Sean

Reputation: 29772

Here's a self-contained subroutine that'll do the job. It uses the special variable $^I, which activates Perl's in-place editing feature. (See the "perlvar" man page for more information on $^I, and the "perlrun" man page for information about the -i command-line switch, which turns on in-place editing.)

use strict;  # Always.

sub scale_numbers_in_file_by_factor {
    my ($path, $scaling_factor) = @_;
    local @ARGV = ($path);
    local $^I = '.bak';
    while (<>) {
        s/ ( -? \d+ \. \d+ ) / $scaling_factor * $1 /gex;
        print;
    }
}

scale_numbers_in_file_by_factor('my-file.txt', .1);

A backup file will be made by appending '.bak' to the original filename. Change the '.bak' to '' above if you don't want a backup.

You might want to tweak your number-recognizing regular expression. As written, it will not match numbers without a trailing decimal point and at least one digit. I think you also want -? to match an optional minus sign, not -*, which will match any number of minus signs. Performing arithmetic on a string with more than one leading minus sign will almost certainly not do what you want.

Upvotes: 4

Roman Cheplyaka
Roman Cheplyaka

Reputation: 38708

s/(-*\d+.\d+)/$scalingFactor*$1/ge

(notice e in the end)

Upvotes: 4

Related Questions