bochen421
bochen421

Reputation: 159

How to replace all characters but for the first and last two with gsub Ruby

Given any email address I would like to leave only the first and last two characters and input 4 asterisks to the left and right of @ character.

The best way to explain are examples:

[email protected] changed to lo****@****om

[email protected] changed fo****@****de

How to do it with gsub?

Upvotes: 1

Views: 3478

Answers (3)

Sarmad Sabih
Sarmad Sabih

Reputation: 166

I have a solution which doesn't fully solve your problem but it's pretty flexible and I think it's worth it to share it for anyone else looking for similar solutions.

module CoreExtensions
  module String
    module MaskChars
      def mask_chars(except_first_n: 1, except_last_n: 2, mask_with: '*')
        if except_first_n.zero? && except_last_n.zero?
          raise ArgumentError, "except_first_n and except_last_n can't both be zero"
        end

        if length < (except_first_n + except_last_n)
          raise ArgumentError, "String '#{self}' must be at least #{except_first_n}"\
            " (except_first_n) #{except_last_n} (except_last_n) ="\
            " #{except_first_n + except_last_n} characters long"
        end

        sub(
          /\A(.{#{except_first_n}})(.*)(.{#{except_last_n}})\z/,
          '\1' + (mask_with * (length - (except_first_n + except_last_n))) + '\3'
        )
      end
    end    
  end
end

Let me explain the regex in /\A(.{#{except_first_n}})(.*)(.{#{except_last_n}})\z/

  • \A - start of string
  • (.#{except_first_n}) or (.{1}) Group 1: first n chars. Default value of except_first_n is 1
  • (.*) Group 2 capturing any 0+ chars as many as possible before the last n characters
  • (.#{except_last_n}) or (.{2}) Group 3: last n chars. Default value of except_last_n is 2
  • \z - end of string

Let me explain what's happening in '\1' + (mask_with * (length - (except_first_n + except_last_n))) + '\3'

We are substituting the string with group 1 (\1) at the start, it'll contain characters equalling except_first_n argument's value. We are not gonna use group 2, we need to replace group 2 with the character from mask_with argument, to calculate the amount of times we need to add mask_with character, we use this formula length - (except_first_n + except_last_n) (total length of the string minus the sum value of except_first_n and except_last_n. This will ensure that we have the exact number of mask_with characters between the except_first_n and the except_last_n characters).

Then I created an initializer file config/initializers/core_extensions.rb with this line: String.include CoreExtensions::String::MaskChars

It will add mask_chars as an instance method to the String class available to all strings.

It should work like this:

account = "123456789101112"
=> "123456789101112"
account.mask_chars
=> "1************12"
account.mask_chars(except_first_n: 3, except_last_n: 4, mask_with: '#')
=> "123########1112"

I think this is a pretty useful method which can be useful in many scenarios and very flexible too.

Upvotes: 0

Wiktor Stribiżew
Wiktor Stribiżew

Reputation: 626748

**If you want to mask with a fixed number of * symbols, you may yse

'[email protected]'.sub(/\A(..).*@.*(..)\z/, '\1****@****\2')
# => lo****@****om

See the Ruby demo.

Here,

  • \A - start of string anchor
  • (..) - Group 1: first 2 chars
  • .*@.* - any 0+ chars other than line break chars as many as possible up to the last @ followed with another set of 0+ chars other than line break ones
  • (..) - Group 2: last 2 chars
  • \z - end of string.

The \1 in the replacment string refers to the value kept in Group 1, and \2 references the value in Group 2.

If you want to mask existing chars while keeping their number, you might consider an approach to capture the parts of the string you need to keep or process, and manipulate the captures inside a sub block:

'[email protected]'.sub(/\A(..)(.*)@(.*)(..)\z/) { 
    $1 + "*"*$2.length + "@" + "*"*$3.length + $4
}
# => lo*********@*******om

See the Ruby demo

Details

  • \A - start of string
  • (..) - Group 1 capturing any 2 chars
  • (.*) - Group 2 capturing any 0+ chars as many as possible up to the last....
  • @ - @ char
  • (.*) - Group 3 capturing any 0+ chars as many as possible up to the
  • (..) - Group 4: last two chars
  • \z - end of string.

Note that inside the block, $1 contains Group 1 value, $2 holds Group 2 value, and so on.

Upvotes: 6

iskvmk
iskvmk

Reputation: 392

Using gsub with look-ahead and look-behind regex patterns:

 '[email protected]'.gsub(/(?<=.{2}).*@.*(?=\S{2})/, '****@****')
=> "lo****@****om"

Using plain ruby:

str.first(2) + '****@****' + str.last(2)
=> "lo****@****om"

Upvotes: 2

Related Questions