Perl Replacing digits with "x" in string, but only in one particular location

perl, regex

Solution

I'd use this solution:

$error =~ s{(\[ [^\]]+ \])}{
  (my $date = $1) =~ tr/0-9/x/;
  $date;
}ex;

This won't work in older perls without a re-entrant regex engine. Apparently, I was wrong. I tried that code with a freshly-brewed perl 5.10.1, and it worked just fine.

Alternatively, you could abuse an lvalue `substr`:

if ($error =~ /\[/gc) {
  my $start  = pos $error;
  my $length = index($error, ']', $start) - $start;
  substr($error, $start, $length) =~ tr/0-9/x/;
}

Problem

I am parsing a log file filled with various errors. These are web errors, and it means that a client made a goof in formatting the date for our website. The log looks like this: ``` Error 123: Customer 2: Bad Date [17/12/2014] Error 123: Customer 2: Bad Date [19/12/2014] Error 123: Customer 1: Bad Date [123/23/222] Error 123: Customer 2: Bad Date [null] Error 123: Customer 6: Bad Date [12/14:] Error 123: Customer 6: Bad Date [12/16:] ``` Now, the first two are really the same error for the same customer. Both lines, the date was reported as `DD/MM/YYYY` instead of `YYYY/MM/DD`, so I don't need to report this error twice. The last two lines are also the same error for the same customer. The used `MM/DD` and left off the year. The `null` date is another error even though I reported Customer #2's Bad Date error before. Somewhere, they're passing a null date. What I'd like to do is compare the lines this way: ``` Error 123: Customer 2: Bad Date [xx/xx/xxxx] Error 123: Customer 2: Bad Date [xx/xx/xxxx] Error 123: Customer 1: Bad Date [xxx/xx/xxx] Error 123: Customer 2: Bad Date [null] Error 123: Customer 6: Bad Date [xx/xx:] Error 123: Customer 6: Bad Date [xx/xx:] ``` Now, it's easy to see that the first two and last two lines are really the same error. The question is how to do this with a regular expression. I want to change all digits between the `[` and `]` to `x`, but I don't want to touch the rest of the string, so I don't want to convert the Error or Customer numbers to `x`. I first tried: ``` $error =~ s/(\[.*?)\d/$1x/g; ``` But that only touches the first digit in the brackets. I've tried it without the non-greedy qualifier, but that only touches the last character. I could simply do this: ``` $error =~ s/\d/x/g; ``` But that replaces all occurrences of a digit with an `x` destroying my Error number and Customer number. I can pass the error line over and over again until there's no more replacement: ``` while ( my $error = <DATA> ) { chomp $error; while ( $error =~ s/(\[.*?)\d/$1x/ ) { 1; } say qq(Error: "$error"); } ``` But there must be a way I can do this without having to loop through a `while` loop multiple times. Is there a way to efficiently replace all occurrences of a digit with an `x`, but only between the two square brackets?

Original source