I have some emails where I would like to mask all characters except the last 2 before the @ sign and mask everything after that.

For example:


Desired output:


So far, using perl, I tried:

perl -pe 's/(<.)(.*)(@.)(.*)(.\..*>)/$1."*" x length($2).$3."*" x length($4).$5/e'

but it is not giving the intended results.

marked as duplicate by ivanivan, Jeff Schaller, G-Man, Romeo Ninov, Sparhawk Sep 12 '18 at 23:44

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • it's similar, but i couldn't post a question on there so i created a new question. – Dang Tran Sep 12 '18 at 13:20
  • 1
    Dang Tran, why is it only "similar" rather than a duplicate? – roaima Sep 12 '18 at 13:37
  • 2
    Your question seems to be garbled. You say “I would like to mask all characters except the last 2 before the @ sign”, but then you show an example masking the last 2 before the @ sign and leaving all characters except the last 2 before the @ sign alone.  Also, you say “… and mask everything after that”, but then you show the . not being masked. – G-Man Sep 12 '18 at 18:24
  • 1
    (Cont’d) …  If we’re not going to allow Dang Tran to post a follow-up question on the other thread  (either as an answer or a comment),  then we have to allow them to ask a new question. – G-Man Sep 12 '18 at 18:35
  • 1
    (Cont’d) …  The text says «put on hold as off-topic by Stephen Harris, Vlastimil, Goro, Thomas, RalfFriedl 10 hours ago // This question appears to be off-topic.  The users who voted to close gave this specific reason: // • "Questions describing a problem that can't be reproduced and seemingly went away on its own (or went away when a typo was fixed) are off-topic as they are unlikely to help future readers." – Stephen Harris, Vlastimil, Goro, Thomas».  So apparently RalfFriedl voted for a different close reason.  But this is the first one I’ve seen in ten days. – G-Man Sep 23 '18 at 23:44
$ perl -pe 's/(.*)(..)(@)(.*)(\.)(.*)(>)/$1."*" x length($2).$3."*" x length($4).$5 ."*" x length($6).$7/e' file

Never used perl, but I can kludge my way through regex.

Match any character as many times as possible:


Match two characters:


Literal @ symbol


Literal .


Then replace those matches with something. We want to keep the first group (the first 5 characters), so just print it:


And print a * for each character we found in the second group:

"*" x length($2)

The rest follows similarly.

  • 1
    Good explanation! – simlev Sep 12 '18 at 14:37
  • @Kevin Kruse thanks for this. very helpful. my input is: <joeblow@joeblow.com>. so the output has the extra * at the end due to this closing/greater than sign >. – Dang Tran Sep 12 '18 at 14:46
  • @DangTran that's different from the text in your question. Do you want to print the brackets in the output as well? e.g. <joebl**@*******.***> ? – Kevin Kruse Sep 12 '18 at 14:52
  • yes i do Kevin. i'm trying to navigate through this site and learn and find the edit button to edit my post. thanks all for understanding. – Dang Tran Sep 12 '18 at 14:56
  • I'll edit my answer. You can use this edit link (found near the bottom of your question) to edit your post. Please include brackets in the input and output when you edit. – Kevin Kruse Sep 12 '18 at 14:58

You could try to understand what the solutions given to this very similar question mean, and adapt them to your case. I understand that if you never met Perl before this can be quite a challenge, though. So, here's your solution:

echo joeblow@joeblow.com | perl -pe 's#..@(.*)#"**@".$1=~s/[^.]/*/gr#e'

Now, go and learn...

The question was edited and the input data changed. Here's an updated answer:

echo '<joeblow@joeblow.com>' | perl -pe 's#..@(.*)#"**@".$1=~s/[^.>]/*/gr#e'
  • This is good: allow for any number of dots in the domain part. – glenn jackman Sep 12 '18 at 14:48
  • thank you simlev. my input has the closing > at the end so the domain part has 4 **** instead of ***. input <joeblow@joeblow.com> – Dang Tran Sep 12 '18 at 14:50
  • @DangTran Just add a ">" then. Please note that your posted input is joeblow@joeblow.com: you should edit your question with any further info. – simlev Sep 12 '18 at 14:53

If obfuscation is your goal, you might not want to give the viewer any clues about the string length of the TLD and domain: try using just a fixed number of stars:

perl -pe 's/..\@.*/**\@******/g'

Update based on revised requirements I read through the comments:

email="blah blah <joeblow@jowblow.com> blah blah <ginger@far.side.larson>"

echo "$email" | perl -pe 's/<.*?\K..\@[^>]+/**\@****/g'
blah blah <joebl**@****> blah blah <ging**@****>

Or with simlev's idea of keeping the dots:

$ perl -pe 's{<.*?\K..\@([^>]+)}{ ($tmp = $1) =~ s/[^.]/*/g; "**\@$tmp" }eg' <<<"$email"
blah blah <joebl**@*******.***> blah blah <ging**@***.****.******>
  • 1
    your comment formatting is a bit messed up, but since perl performs variable interpolation on both sides of the s/// command, we don't want the @ interpreted as the start of an array variable. As for the g the content of the input is unknown: although the .* will slurp up the rest of the line. – glenn jackman Sep 12 '18 at 14:47
  • Yeah, the comment formatting didn't come out as expected! Thanks for the explanation, I guess it's just safer this way. Totally agree on the suggestion to not preserve the original string length, although there might be a case for both options as it really depends on the purpose. – simlev Sep 12 '18 at 14:58

Not the answer you're looking for? Browse other questions tagged or ask your own question.