I want capture number that last two occurrence.
I make code like this:
 perl -E '$s="abc-2de3-fg44-5.jpg"; $s=~s/(\d+)(?!.*\d+.*\d+)/sprintf("%03d",$&)/ge;say $s'
Result is abc-2de3-fg044-005.jpg
That is ok, but I found bug:
perl -E '$s="abc-2de3-fg44-55.jpg"; $s=~s/(\d+)(?!.*\d+.*\d+)/sprintf("%03d",$&)/ge;say $s'
result is abc-2de3-fg44-055.jpg
When filename changes from 5.jpg to 55.jpg, there is no change from fg44 to fg044.
I don't know how to handle this.
You really should have more test cases, or a more detailed description of the target strings.
When combining .* and negative look-around assertions, you have to be careful. The wildcard sequence .* means "match anything, or nothing" (except newline without the /s switch). You expect your negative lookahead assertion to say "make sure there are not two more clusters of digits", but what you are really saying is "make sure there are not two digits or more ahead".
Because of the use of negative lookahead and wildcard strings, these regexes are also relatively inefficient, with much backtracking. A much more consistent and simple way to check would be to check for the two last digit groups.
Instead of checking what cannot be after, determine what should be after. By using a lookahead assertion.
while (<DATA>) {
    s/(\d+)(?=(?:\D+\d+)?\D+$)/sprintf("%03d",$1)/ge;
    print;
}
__DATA__
abc-2de3-fg44-5.jpg
abc-2de3-fg44-55.jpg
The trick here is using the optional group (?: ... )?, where the ? quantifier means "match 0 or 1 time". So it will look for a group of digits that are followed by only non-digits \D until end of line $, with an optional group of digits.
More clearly, this will match the last digit group:
(\d+)(?=\D+$)
and this the next to last digit group:
(\d+)(?=\D+\d+\D+$)
If you want to expand the limited match to the last three digits, you use quantifier {0,2} instead.
The negative lookahead (?!.*\d+.*\d+) that you use, means that there should not be 2 digits to the right from the current position, and note that the dot can also match a digit.
It matches the "55" in "abc-2de3-fg44-55.jpg" because after matching "55" (by the leading (\d+) in your regex), that negative lookahead assertion is true.
It does not match "44" because after matching that, there are 2 digits to the right.
After matching the digits, you could assert that from the current position, there are not 2 occurrences of a digit where there is at least 1 non digit in between.
If you want the match only, you don't have to use a capture group.
You might use:
\d+\b(?!\D+\d+\D+\d)
The regex matches:
\d+\b Match 1+ digits followed by a word boundary to prevent partial matches(?! Negative lookahead, assert that what is directly to the right is not
\D+\d+\D+\d Match 1+ non digits, 1+ digit, 1+ non digits and then at least 1 digit) Close the lookaheadSee a demo on regex 101.
If there can also be a word character after the matched digits, instead of using a word boundary you could assert that there is not a digit following.
\d+(?!\d)(?!\D+\d+\D+\d)
Example using your provided code:
perl -E '$s="abc-2de3-fg44-55.jpg"; $s=~s/\d+\b(?!\D+\d+\D+\d)/sprintf("%03d",$&)/ge;say $s'
Output
abc-2de3-fg044-055.jpg
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With