11

I have a strings:

AddData
TestSomething
TellMeWhoYouAre

and so on. I want to add space before uppercase letters. How can I do it?

2
  • 7
    What do you want to do when there are consecutive upper case letters? example IClimbALadder Commented Feb 1, 2017 at 15:26
  • 1
    Actually I have a strings like ReadFileFromCDDrive and @Kusalananda 's solution works great. Commented Feb 2, 2017 at 6:06

6 Answers 6

20

Using sed, and assuming you don't want a space in front of the word:

$ sed 's/\([^[:blank:]]\)\([[:upper:]]\)/\1 \2/g' file.in
Add Data
Test Something
Tell Me Who You Are

The substitution will look for an upper-case letter immediately following a another non-whitespace character, and insert a space in-between the two.

For strings with more than one consecutive upper-case character, like WeAreATeam, this produces We Are ATeam. To sort this, run the substitution a second time:

$ sed -e 's/\([^[:blank:]]\)\([[:upper:]]\)/\1 \2/g' \
      -e 's/\([^[:blank:]]\)\([[:upper:]]\)/\1 \2/g' file.in
4
  • @Kusalananda, thanks for your answer. I've tried your solution with my case and it works well except the only one problem when I've tried it with ReadFileFromUSBDrive Commented Feb 2, 2017 at 6:13
  • @HeroFromEarth What would you want that string to be turned into? Read File From U S B Drive or Read File From USBDrive?
    – Kusalananda
    Commented Feb 2, 2017 at 7:39
  • @Kusalananda I want to turn it to Read File From USB Drive Commented Feb 2, 2017 at 10:10
  • Shorter line if you don't have a space at the start (are using camelCase): sed 's|\([[:upper:]]\)| &|g'
    – Gamma032
    Commented Feb 2, 2022 at 22:50
13

Perl, using lookbehind and lookahead zero-width regular expressions:

$ perl -pe 's/(?<=\w)(?=[A-Z])/ /g'  file.in 

Tell Me Who You Are                    ## TellMeWhoYouAre
I Am A Regular Expression User         ## IAmARegulaExpressionUser

This version is also separating consecutive uppercase letters.

3
  • 1
    This turns ReadFileFromUSBDrive into Read File From U S B Drive whereas the OP wanted Read File From USB Drive.
    – Kusalananda
    Commented Feb 2, 2017 at 12:54
  • 2
    @Kusalananda, thank you for pointing it out. (I'm afraid I dont see that written in the question). In real situations (programming understanding, id words expanding, and CamelCase variants) it is common to use a basic criteria (either split in single Uppercase or the opposite) and have a dictionary of exceptions.
    – JJoao
    Commented Feb 2, 2017 at 16:51
  • 2
    Sorry, it was something the OP wrote in the comments to my answer. I agree, it's difficult to do this without a word list of some kind.
    – Kusalananda
    Commented Feb 2, 2017 at 16:53
2
sed -r -e "s/([^A-Z])([A-Z])/\1 \2/g"

Add space between a letter that is not an upper-case letter and a letter that is an upper-case letter

0
2

Came here to get this question answered and, after reading the other suggestions, arrived at a solution that handles the ReadFromUSBDrive to Read From USB Drive case, too - I adapted ka3ak's expression style and kusalananda's use of multiple expressions to get the following:

 sed -r -e "s/([^A-Z])([A-Z])/\1 \2/g" -e "s/([A-Z]+)([A-Z])/\1 \2/g" file.in

e.g.

$ sed -r -e "s/([^A-Z])([A-Z])/\1 \2/g" -e "s/([A-Z]+)([A-Z])/\1 \2/g" <<EOF
AddData
TestSomething
TellMeWhoYouAre
ReadFromUSBDrive
EOF
Add Data
Test Something
Tell Me Who You Are
Read From USB Drive

Note that this will choke on single-letter words like the article "A" or the pronoun "I" when they occur directly before an abbreviation as they would be indistinguishable from the characters in an intended abbreviation, e.g. ReadAUSBDrive will render Read AUSB Drive.

0

Python solution:

#!/usr/bin/env python
from __future__ import print_function
import sys

with open(sys.argv[1]) as f:
    for line in f:
        for char in line:
            if char.isupper():
               print(" "+char,end="")
            else:
               print(char,end="")

Test run:

$ ./add_space_to_upper.py input.txt                        
 Add Data
 Test Something
 Tell Me Who You Are
1
  • You want print(line[0], end="") followed by for char in line[1:]: to avoid printing that unwanted space at the beginning of every output line.
    – Paul Evans
    Commented Feb 5, 2017 at 12:00
0

Using Raku (formerly known as Perl_6)

Sample Input:

AddData
TestSomething
TellMeWhoYouAre
IClimbALadder
ReadFileFromCDDrive
ReadAUSBDrive

Code and Output (1):

raku -pe 's:g/(<?after <:Ll> >) (<?before <:Lu>+ >)/ /;' 

Add Data
Test Something
Tell Me Who You Are
IClimb ALadder
Read File From CDDrive
Read AUSBDrive

Code and Output (2):

raku -pe 's:g/(<?after <:L> >) (<?before <:Lu>+ >)/ /;' 

Add Data
Test Something
Tell Me Who You Are
I Climb A Ladder
Read File From C D Drive
Read A U S B Drive

Above is a pretty direct translation of @JJoao's Perl5 code. The <:L>, <:Ll>, and <:Lu> represent "Unicode letter", "Unicode letter-lower", and `Unicode letter-upper", respectively.

Addendum: Like others in this thread have noted, it appears that sequential s/// substitution calls can get you closer to a desired output. Below, take the results of the first Raku example (above), and run a second s/// substitution:

Code and Output (3):

raku -pe 's:g/(<?after <:Ll> >) (<?before <:Lu> >)/ /;'  | perl6 -pe 's:g/(<?after <:Lu>+ >) (<?before <:Lu><:Ll>+ >)/ /;'

Add Data
Test Something
Tell Me Who You Are
I Climb A Ladder
Read File From CD Drive
Read AUSB Drive

The results with Raku (5/6 test lines separated correctly), equals the best results obtained with other languages posted thus far.

http://raku.org

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .