Trent Whiteley
Trent Whiteley

Reputation: 81

Regex: mask all but the last 5 digits, ignoring non-digits

I want to match a number containing 17-23 digits interspersed with spaces or hyphens, then replace all but the last five digits with asterisks. I can match with the following regex:

((?:(?:\d)([\s-]*)){12,18})(\d[\s-]*){5}

My problem is that I can't get the regex to group all instances of [\s-] in the first section, and I have no idea how to get it to replace the initial 12-18 digits with asterisks (*).

Upvotes: 2

Views: 2039

Answers (4)

Denn0
Denn0

Reputation: 397

To give a variant to Alan Moore's answer and using all word characters [a-zA-Z0-9] as \w instead of just digits \d. This will also work with any length string.

public String maskNumber(String number){
    String regex = "\\w(?=(?:\\W*\\w){4,}(?!\\W*\\w))";
    Pattern p = Pattern.compile(regex);
    Matcher m = p.matcher(number);
    while(m.find()){
        number = number.replaceFirst(m.group(),"*");
    }
    return number;
}

This example

String[] numbers = {
        "F4546-6565-55654-5457",
        "F4546-6565-55654-54-D57",
        "F4546-6565-55654-54-D;5.7",
        "F4546-6565-55654-54-g5.37",
        "hd6g83g.duj7*ndjd.(njdhg75){7dh i8}",
        "####.####.####.675D-45",
        "****.****.****.675D-45",
        "**",
        "12"
};

for (String number : numbers){
    System.out.println(maskNumber(number));
}

Gives:

*****-****-*****-5457
*****-****-*****-*4-D57
*****-****-*****-*4-D;5.7
*****-****-*****-**-g5.37
*******.*********.(*******){*dh i8}
####.####.####.**5D-45
****.****.****.**5D-45
**
12

Upvotes: 0

Alan Moore
Alan Moore

Reputation: 75222

How about this:

s/\d(?=(?:[ -]*\d){5,22}(?![ -]*\d))/*/g

The positive lookahead insures that there are at least 5 digits ahead of the just-matched digit, while the embedded negative lookahead insures that aren't more than 22.

However, there could still be more digits before the first-matched digit. That is, if there are 24 or more digits, this regex only operates on the last 23 of them. I don't know if that's a problem for you.

Upvotes: 5

user557597
user557597

Reputation:

I think your regex is ok, but you might need to have a callback where you can insert the asterisks with another inline regex. The below is a Perl example.

s/((?:\d[\s-]*){12,18})((?:\d[\s-]*){4}\d)/ add_asterisks($1,$2) /xeg

use strict;
use warnings;

my $str = 'sequence of digits 01-2  3-456-7-190 123-416 78 ';

if ($str =~ s/((?:\d[\s-]*){12,18})((?:\d[\s-]*){4}\d)/ add_asterisks($1,$2) /xeg )
{
   print "New string: '$str'\n";
}

sub add_asterisks {
   my ($pre,$post) = @_;
   $pre =~ s/\d/*/g;
   return $pre . $post;
}

__END__

Output

New string: 'sequence of digits **-* *-***-*-*** ***-416 78 '

Upvotes: 1

CAFxX
CAFxX

Reputation: 30291

Even assuming that this is feasible with regex alone I'd bet that it would be way slower than using the non-capturing version of your regex and then reverse iterating over the match, leaving the first 5 digits alone and replacing the rest of them with '*'.

Upvotes: 1

Related Questions