$s = "bla..bla";
$s =~ s/([^%])\./$1/g;
When a global regular expression is searching a string it will not find overlapping matches.
The first match in your string will be
a., which is replaced with
a. When the regex engine resumes searching it starts at the next
. so it sees
.bla as the rest of the string, and your regex requires a character to match before the
. so it cannot match again.
Instead, use a negative lookbehind to perform the assertion that the previous character is not
$s =~ s/(?<!%)\.//g;
Note that if you use a positive lookbehind like
(?<=[^%]), you will not replace the
. if it is the first character in the string.