2

I have a file that contains many lines like the following:

some text { {{7.718 1072.463} {7.718 1230.985} {15.724 1230.985} {15.724 1198.742} {15.759 1198.742} } {{7.702 1072.489} {7.702 889.858} {13.418 889.858} {13.418 887.367} {13.435 887.367} {13.435 887.376} {7.144 887.376} } } some more text

Each pair of values are X and Y coordinates. For each line in the file, I want to find every X value and add a certain value (lets say 6.3) and print that new line. The X value will always follow a { without any space. I am able to get each X value in a single line with this while loop:

 while ($line=~/\{\d+(?:\.\d+)?/g) {
     $oldX=~s/\{//;
 }

But how can I add a value (like 6.3) to every X occurrence of X value I find the string? I want the new string to look just like the original except each X value should be increased by 6.3.

Thank you for any guidance on this.

TLP
  • 66,756
  • 10
  • 92
  • 149
daryl
  • 23
  • 2

2 Answers2

1

$line =~ s/\{\K(\d+(?:\.\d+)?)/$1 + 6.3/ge will replace each number immediately following a curly bracket ({) with the same number increased by 6.3.

The \K keeps the curly brace. The /e flag to s/// evaluates the expression in the replacement part of the substitution. And g flag replaces every occurrence of the match.

If you need to match numbers that start with a dot (e.g. .25), try:

$line =~ s/\{\K(\d+(?:\.\d+)?|\.\d+)/$1 + 6.3/ge

You can replace 6.3 in the regex with the variable containing the amount to add.

Nathan Mills
  • 2,243
  • 2
  • 9
  • 15
  • 2
    Swapping delimiter `/` for `!` without a good reason is probably a bad idea and serves only as obfuscation. – TLP Mar 06 '21 at 13:42
0

You could also only make use of \K and use the full match using $& in the replacement.

Using \K will reset the starting point of the reported match (forgets what is matched so far)

The pattern matches:

  • \{ Match {
  • \K Forget what is matched until so far
  • \d+ Match 1+ digits
  • (?:\.\d+)? Optionally match a dot and 1+ digits

See a Regex demo or a Perl demo

For example

$line =~ s/\{\K\d+(?:\.\d+)?/$& + 6.3/ge;
print($line);

Output

{ {{14.018 1072.463} {14.018 1230.985} {22.024 1230.985} {22.024 1198.742} {22.059 1198.742} } {{14.002 1072.489} {14.002 889.858} {19.718 889.858} {19.718 887.367} {19.735 887.367} {19.735 887.376} {13.444 887.376} } }

If you want to assert a closing } at the right, you can make use of a positive lookahead.

\{\K\d+(?:\.\d+)?(?=[^{}]*})

Regex demo

The fourth bird
  • 154,723
  • 16
  • 55
  • 70
  • 2
    Swapping delimiter `/` for `~` without a good reason is probably a bad idea and serves only as obfuscation. – TLP Mar 06 '21 at 13:44
  • In recent perls you need to escape literal left braces https://www.effectiveperlprogramming.com/2017/04/you-must-escape-the-left-brace-in-a-regex/ – brian d foy Mar 06 '21 at 14:39
  • It's only one of the reasons, including the weird delimiter and not explaining what \K does. But, then, code that won't compile is usually a sufficient reason. – brian d foy Mar 06 '21 at 14:46