5

Is there a way to split CamelCase words at the "internal" word boundaries in a text?

For example, given the string:

IamHelloTest forYou PickTest; 

as input, I would like to produce as output:

Iam
Hello
Test
for
You
Pick
Test

Update: now that the question has many helpful answers, how would I proceed to include the following cases too?

Input:

IamTestECHO TEST PickFoo BARFull;

Desired output:

I
am
Test
ECHO
TEST
Pick
Foo
Bar
FULL

Update: how would I proceed to include the underscore too?

Input:

IamTestECHO TEST PickFoo BARFull def_python_FunctionTwo;

Desired output:

I
am
Test
ECHO
TEST
Pick
Foo
Bar
FULL
def
python
Function
Two
3
  • Your pattern is unclear. Please edit your question to include more details. What exactly is the condition?
    – Panki
    Mar 6 '20 at 8:19
  • Note: "PickTest" is commonly called PascalCase, distinguished from "forYou" camelCase. Mar 7 '20 at 1:01
  • Please do not change or amend the question after answers have already been given without at least an additional notice; it makes understanding the answers difficult for readers who did not follow the time-evolution of your post.
    – AdminBee
    Mar 9 '20 at 12:19
7

If your grep implementation supports -o (and is not the ast-open implementation which chokes with -o for regexps that match the empty string):

grep -o '[[:upper:]]*[[:lower:]]*'
3
  • 2
    Nice, a GNU grep / pcre version would be grep -Po '\p{Lu}*\p{Ll}*'. But that will not separate the I from the Am in IAmTheMountain (which admittedly, may be EXACTLY what the OP wants).
    – mosvy
    Mar 6 '20 at 9:17
  • @mosvy I would think it wouldn't be desirable. It can be fixed with '[[:upper:]]\?[[:lower:]]*' or grep -Po '\p{Lu}?\p{Ll}*' as per your PCRE version.
    – JoL
    Mar 7 '20 at 1:08
  • it just output Iam Hello Test, the words after space not printed
    – John Chen
    Mar 7 '20 at 1:15
4

With GNU grep, using Unicode character properties and zero-width assertions:

grep -Po '((?<!=\p{Lu})\p{Lu}|(?<!=\pL)\pL)\p{Ll}*'

$ echo 'IamHelloTest forYou PickTest;' | grep -Po '((?<!=\p{Lu})\p{Lu}|(?<!=\pL)\pL)\p{Ll}*'
Iam
Hello
Test
for
You
Pick
Test
$ echo 'АямГеллоТест форЮ ПикТест' | grep -Po '((?<!=\p{Lu})\p{Lu}|(?<!=\pL)\pL)\p{Ll}*'
Аям
Гелло
Тест
фор
Ю
Пик
Тест
2

To deal with your second example, a suggest a more "rule based" approach. Consider the following Perl script (camelcaseproc):

#!/usr/bin/perl -CSDA -p

s{  \W+                                     # break on non-word
 |  _                                       # break on "_"
 |  (?<=\p{Ll})(?=\p{Lu})                   # ...aB... → ...a-B...
 |  (?<=\p{Lu})(?=\p{Lu}\p{Ll})             # ..ABCd.. → ...AB-Cd.
 |  (?<=I)(?=am)                            # exceptions rules
 }{-}xg                                     # 
  • Line 1: use Unicode (to process accents, Cyrillic)
  • Line 2: substitute non-letters by "\n"
  • line 3,4,5: break-intraWord rules (defined by left context, rigth context)
  • line 5: exception rules for "Iam"
  • line 5: x option makes possible to add comments in regular expressions

After the usual chmod +x camelcaseproc we can use it as:

$ camelcaseproc <<< "IamTestECHO TEST PickFoo BARFull"
I-am-Test-ECHO-TEST-Pick-Foo-BAR-Full

$ camelcaseproc input-file

$ echo "IamTestECHO TEST PickFoo BARFull" | camelcaseproc
7
  • hello, can you organize the command like this? echo "IamTestECHO TEST PickFoo BARFull" | perl -n -e "xxx"
    – John Chen
    Mar 9 '20 at 10:11
  • 1
    The solution presented can be used as echo "IamTestECHO TEST PickFoo BARFull" | ./camelcaseproc ; it is possible to echo "...." | perl -CSDA -pe 's{\W+|(?<=\p{Ll})(?=\p{Lu})|(?<=\p{Lu})(?=\p{Lu}\p{Ll})|(?<=I)(?=am)}{\n}g' but it is much harder to maintain.
    – JJoao
    Mar 9 '20 at 11:02
  • it's fantasy, thank you. if I have the underscore in the words, how can do this?
    – John Chen
    Mar 10 '20 at 6:58
  • 1
    Assuming you want it to be a separator, we can add a new rule (one more pattern - see my edit)
    – JJoao
    Mar 10 '20 at 11:54
  • unix.stackexchange.com/questions/565043/… I have another more harder question, can you have a try?
    – John Chen
    Mar 11 '20 at 15:38
1

With sed:

sed -Ee 's/([a-z])([A-Z])/\1\n\2/g' < your_file

With grep:

grep -Eo '[A-Z][a-z]+' < your_file
5
  • I just tried out your suggestions, and they don't seem to work (at least with the GNU variants): the sed call merges fields across separating space (as in Test for, You Pick), and the grep call doesn't work with words that start with a lower-case character ...
    – AdminBee
    Mar 6 '20 at 8:49
  • 1
    Maybe edit your question to be more specific about what should happen around "whitespace", beginning and end of words (starting/ending with uppercase/lowercase), punctuation etc... And adding a second stage to split lines on white space is rather trivial...
    – xenoid
    Mar 6 '20 at 10:53
  • You're missing the opening single quote to quote the sed expression. Also, while -r does work with GNU sed, -E is the POSIX way to request POSIX RE syntax. Mar 7 '20 at 1:04
  • @DavidConrad thanks, fixed
    – xenoid
    Mar 7 '20 at 1:25
  • Well, it is not my question (I only edited it), but I thought the output examples provided were rather clear on what should happen ...
    – AdminBee
    Mar 9 '20 at 12:12

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.