Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I have the following list of items:

bigBone
fishMarket
dogCollar
...

I need to generate a two character mapping like this:

bigBone -> bb
fishMarket -> fm
dogCollar -> dc
...

How can I create the above mapping using sed?

I have tried something like:

sed -i -r 's/^([a-z]{1})[a-z]+([A-Z]{1})[a-zA-Z]+/ -> \1\L\2/' file

I saw this question, but not sure how to incorporate the concept here. Thanks.

share|improve this question
    
See the comment of Stephane Chazelas to my answer. –  Hauke Laging May 6 at 9:11

6 Answers 6

up vote 3 down vote accepted

If I understand you correctly then you want to keep the whole line and just append something:

sed -r 's/^([a-z]{1})([a-z]+)([A-Z]{1})([a-zA-Z]+)$/\1\2\3\4 -> \1\L\3/' file

edit:

devnull had to remind me of it that there is an easy solution to this:

sed -r 's/^([a-z]{1})[a-z]+([A-Z]{1})[a-zA-Z]+/& -> \1\L\2/' file

Or, a bit more elegant (than my first try):

sed -r '
h
s/^([a-z]{1})[a-z]+([A-Z]{1})[a-zA-Z]+/ -> \1\L\2/
t append
b
: append
H
g
s/\n//' file
share|improve this answer
    
Thanks, I didn't know it could be so simple. –  Question Overflow May 6 at 7:47
    
@QuestionOverflow It is even simpler, see the edit. –  Hauke Laging May 6 at 8:39
1  
ranges like [a-z] only make sense in the POSIX or C locales. So you'd want either LC_ALL=C sed ... or to use [[:lower:]] instead. Note that {1} is redundant. –  Stéphane Chazelas May 6 at 9:05

Here it is with a POSIX compliant script. I wasn't aware that POSIX sed leaves the behavior of s///[n]g unspecified, but, sure enough, it is not a well-defined behavior. Anyway, it's easy to handle without it - I just don't like using too many back-references if it can be helped, usually.

sed '/^[a-z]*[A-Z].*$/{ h
    s/\(.\)[^A-Z]*/\1/g
    y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/
    H ; g 
    s/\n/ -> /
    }' <<\DATA
bigBone
fishMarket
dogCollar
DATA

#OUTPUT
bigBone -> bb
fishMarket -> fm
dogCollar -> dc
share|improve this answer
1  
Have you tried that? Doesn't work here (upper case vs. lower case)). –  Hauke Laging May 6 at 7:44
    
@HaukeLaging - I just needed to fix y/ - it would have worked as it was if I had used -r but there's no need whatsoever to write a non-portable script for such a simple application. If it was you who downvoted this post and you did so due to the case conversion, that issue is resolved, and it is now faster and more portable than your own script. I believe you should reverse that vote. –  mikeserv May 6 at 7:57
    
@HaukeLaging - even better now. Thank you, Hauke. I expect that was you. –  mikeserv May 6 at 8:18
    
Speed? A rare argument with sed. More interesting may be what happens if your and my version hit a line which does not match this pattern ("foo bar")... Sure, that may be impossible depending on the input. –  Hauke Laging May 6 at 8:44
    
@HaukeLauking - sed is made for speed! In any case, your point on mispatched patterns is very good - and it is another way to improve speed. Just a moment... –  mikeserv May 6 at 9:00

Using GNU sed:

sed -r 's/(.)[^[:upper:]]*(.).*/& -> \1\L\2/' inputfile

For your input, it'd produce:

bigBone -> bb
fishMarket -> fm
dogCollar -> dc
share|improve this answer

To generalise to fooBarBaz -> fbb, abCdEfGh -> aceg, with GNU sed:

sed -r 's/(.)(.*)/\1\n\2 -> \L\1/;:1
        s/\n([^[:upper:]]*([[:upper:]]))(.*)/\1\n\3\L\2/;t1;s/\n//'

POSIX sed doesn't have \L. So portably, you'd have to resort to using y and manually enter all the characters you want converted to lowercase. Something like:

LC_ALL=C sed '/^\([[:alpha:]]\).*/{
h;s//\1/;y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/;G
s/\(.\).\(.\)\(.*\)/\2\
\3 -> \1/;:1
/.*\n[^A-Z]*\([A-Z]\).*/{h;s//\1/
y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/;G
s/^\(.\)\n\(.*\)\n\([^A-Z]*[A-Z]\)\(.*\)/\2\3\
\4\1/;t1
}
s/\n//;}'
share|improve this answer

This answer is just similar to @devnull's,

$ sed 's/\(.\).*\([A-Z]\).*/& -> \1\L\2/g' file
bigBone -> bb
fishMarket -> fm
dogCollar -> dc
share|improve this answer

If you can use perl, try:

$ perl -MList::Util=first -F// -aple '$_ .= " -> ".$F[0].lc(first{ord() < 97} @F)' file
bigBone -> bb
fishMarket -> fm
dogCollar -> dc
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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