4

So I have a long list of email address and I would like to sort first by domain, so I would like to take a line like this:

email: vgnerov@yandex.ru
email: petebll@josephay905s.changeip.com

and get this:

ru.yandex email: vgnerov@yandex.ru
com.changeip.josephay905s email: petebll@josephay905s.changeip.com

How would I do that?

here is a larger dataset:

email: nataliyarezvaya@rambler.ru
email: natalyasoborova1975@rambler.ru
email: noelmrunyon@gmail.com
email: novikova33novickova@yandex.ru
email: o9yp7@gmail.com
email: ontorsckyvladislav@yandex.ru
email: p.ri.me.grani.t2.01.9@gmail.com
email: p3ost51@moail.ru
email: pavelsib@meta.ua
email: pedrika@yandex.ru
email: petebll@josephay905s.changeip.com
email: philip@hugosinbox.xyz
email: pjkraik@zfilm6.ru
email: post@b-monitor.ru
2
  • didn't know sort could do that, so there is a problem, all the meta.ua, yonder.ru, etc are together, but the yandex.ru and ebstream.ru are not. It looks like it is simply sorting on everything after the @ symbol. – Sam Carleton Jan 31 '20 at 11:35
  • (1) The above comment appears to be a response to something?  What?  You didn’t know that sort could do what? (2) Having a non-trivial (input) dataset is better than having (only) a trivial (input) dataset.  But it’s not very useful if you don’t also show the expected output. – G-Man Says 'Reinstate Monica' Feb 2 '20 at 8:24
3
#sed -r -n 's/^([^@]+@)(.+)\.([a-z]{2,3})[\r\n\t ]{0,}$/\3.\2 \1\2.\3/gip'  <<< "email: petebll@josephay905s.changeip.com"

or

#sed -r -n 's/^([^@]+@)(.+)\.([a-z]{2,3})[\r\n\t ]{0,}$/\3.\2 \1\2.\3/gip'  ./your file

upd:FIXED TO SUPPORT A THIRD DOMAIN

sed -r -n 's/^([^@]+@)([^\.]+)(\.[^\.]+){0,1}\.([a-z]{2,3})[\r\n\t ]{0,}$/\4\3.\2 \1\2\3.\4/gip'  <<< "email: vgnerov@yandex.ru"
result: ru.yandex email: vgnerov@yandex.ru

and

sed -r -n 's/^([^@]+@)([^\.]+)(\.[^\.]+){0,1}\.([a-z]{2,3})[\r\n\t ]{0,}$/\4\3.\2 \1\2\3.\4/gip'  <<< "email: petebll@josephay905s.changeip.com"
result: com.changeip.josephay905s email: petebll@josephay905s.changeip.com

Thanks for the comment @TERDON

2
  • 1
    This assumes only one . in the domain name, so fails for the second example in the OP. Your command prints com.josephay905s.changeip email: instead of com.changeip.josephay905s email:. – terdon Jan 31 '20 at 9:47
  • Quite impressive! Unfortunately, regular expressions can't handle arbitrarily deep nesting. So you might be able to write a regular expression that handles up to 3 dots, or up to 4 dots, but at some point there will be a domain that it can't reverse. To do that you need a language with arrays (or at least a stack), like awk, perl, or python. – AndyB Feb 1 '20 at 6:06
6

try:

awk -F'@' '
{ split($2, flip, "."); 
  for (i=length(flip); i>=1; i--) printf flip[i] (i!=1?".":" "); 
  print $0;
}' infile
  • define @ as field delimiter with -F'@'
  • split the second field on dot . separator to an array called flip
  • loop over elements of array from last to first and printf each and print back . except for first element; then print whole line $0.

note: for the awk that doesn't support array_length ( see AWK - How to count stored or index on array), try below instead that is first finding how many elements the array taken and use it as max in for-loop, like:

awk -F'@' '
{ split($2, flip, ".");
  max=i=0; for (elements in flip) max++;
  for (i=max; i>=1; i--) printf flip[i] (i!=1?".":" ");
  print $0;
}' infile
2
  • I get the following: .yandex email: pedrika@yandex.ru and .changeip.josephay905s email: petebll@josephay905s.changeip.com – Sam Carleton Jan 31 '20 at 11:34
  • see the update. – αғsнιη Jan 31 '20 at 16:18
5

You can do this perl if you insist on doing this in one line. Basically the -F flag is the same as awk so it splits each line on the @ character. The first part of the one liner creates a variable named $s that has the reversed part of the domain. The second part of the one liner prints out the reversed domain followed by the original input that is stored in the $_ variable.

perl -F'@ ' -lane '$s = join ".", reverse split/\./, $F[-1]; print "$s $_"'
2
  • so I assumed that I needed to do a cat file.txt | <your perl script>, when I do, I get the following: .yandex email: pedrika@yandex.ru and .changeip.josephay905s email: petebll@josephay905s.changeip.com – Sam Carleton Jan 31 '20 at 11:29
  • 1
    Nice answer. So could shorten it down to just perl -F'@ ' -lanpe '$_ = join(".", reverse split/\./, $F[-1]) . $_' And @SamCarleton - just pop the file.txt as first argument, no need to pipe cat into the perl. – steve Jan 31 '20 at 16:43
1

If you're open to using python the code is slightly more readable:

Python 2:

import sys
from string import split

for line in sys.stdin:
    line = line.rstrip()
    rev_domain = '.'.join(split(split(line, '@')[1], '.')[::-1])
    print rev_domain, line

Python 3:

import sys

for line in sys.stdin:
    line = line.rstrip()
    rev_domain = '.'.join(line.split('@')[1].split('.')[::-1])
    print(rev_domain, line)
1

I've directly addressed your requirement, "to sort first by domain", rather than simply creating an extra column at the beginning of each line ready for a linewise sort:

sort -t@ -k2,3 -k1,2 file

Output for "larger dataset"

email: post@b-monitor.ru
email: noelmrunyon@gmail.com
email: o9yp7@gmail.com
email: p.ri.me.grani.t2.01.9@gmail.com
email: philip@hugosinbox.xyz
email: petebll@josephay905s.changeip.com
email: pavelsib@meta.ua
email: p3ost51@moail.ru
email: nataliyarezvaya@rambler.ru
email: natalyasoborova1975@rambler.ru
email: novikova33novickova@yandex.ru
email: ontorsckyvladislav@yandex.ru
email: pedrika@yandex.ru
email: pjkraik@zfilm6.ru

To address the changed requirement in your comment, where you want domain components to be grouped together from "most significant" to "least significant", try this instead

rev file | sort | rev

Revised output for "larger dataset"

email: pavelsib@meta.ua
email: o9yp7@gmail.com
email: p.ri.me.grani.t2.01.9@gmail.com
email: noelmrunyon@gmail.com
email: petebll@josephay905s.changeip.com
email: pjkraik@zfilm6.ru
email: p3ost51@moail.ru
email: natalyasoborova1975@rambler.ru
email: nataliyarezvaya@rambler.ru
email: post@b-monitor.ru
email: pedrika@yandex.ru
email: novikova33novickova@yandex.ru
email: ontorsckyvladislav@yandex.ru
email: philip@hugosinbox.xyz
4
  • Yes, I did say 'sort by domain' but I also am human and f*@k up from time to time. But I did provide a desired output... the reason I did was so folks would see what my objective was despite the fact I ain't no good with words at times... I dont' know what they are called, but that final part, all the letters a-zA-Z between the last period in the file & end of the line, I want those all to be grouped together. Then all the second to last words grouped together, and so on until you get to the @ symbol. In the end, I want ALL the ru together, all the coms together, etc. – Sam Carleton Jan 31 '20 at 12:09
  • That's a really different requirement, and not one your two line example output demonstrates. Please check out my updated answer @SamCarleton. – roaima Jan 31 '20 at 14:13
  • I’m surprised that the OP hasn’t responded to say that this (rev | sort | rev) isn’t what the question asks for, either.  This would put (for example) .ba, .ca, .ga, .ua and .za, etc., together at the beginning, and then .bb, .lb and .sb, and then .cc, .ec and .vc, and so on.  But it looks like he wants the domain names sorted by TLD first, so .ad, .ae, .af and .ag should be first, and .ua and .za should be near the end. – G-Man Says 'Reinstate Monica' Feb 2 '20 at 8:12
  • @G-Man so am I, but there comes a point where it's not worth trying to second-guess what's actually required. Grouping is not sorting, and there's no mention of sorting in the modified requirement. – roaima Feb 2 '20 at 9:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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