Home > Software engineering >  Vim replace several similar elements in a substring of a line
Vim replace several similar elements in a substring of a line

Time:11-05

I needed to format a lot of JSON entries. The goal was to replace the value part with Title Cased versions of the text that was already there, and use spaces instead of an underscore. The entries looked similar to this:

"ASDF": "TUOJKLDSF",
"ASDF": "TUOJKLDSF_FUGZIHOJKOWG",
"ASDF": "TUOJKLDSF_FUGZIHOJKOWG_IKEH",
"ASDF": "TUOJKLDSF_FUGZIHOJKOWG_IKEH_SDF",

And I was trying to get them to look like this:

"ASDF": "Tuojkldsf",
"ASDF": "Tuojkldsf Fugzihojkowg",
"ASDF": "Tuojkldsf Fugzihojkowg Ikeh",
"ASDF": "Tuojkldsf Fugzihojkowg Ikeh Sdf",

The trouble I had is that I needed to keep the first part the same, so I couldn't just replace all A-Z instances, and I couldn't figure out a good way of doing it all in one command.

What I ended up doing is writing a separate command for each number of "words" in the value:

:%s/\([^:\n]\ : "\)\([A-Z]\)\([A-Z]\ \)"/\1\2\L\3"

"ASDF": "Tuojkldsf",
"ASDF": "TUOJKLDSF_FUGZIHOJKOWG",
"ASDF": "TUOJKLDSF_FUGZIHOJKOWG_IKEH",
"ASDF": "TUOJKLDSF_FUGZIHOJKOWG_IKEH_SDF",

:%s/\([^:\n]\ : "\)\([A-Z]\)\([A-Z]\ \)_\([A-Z]\)\([A-Z]\ \)"/\1\2\L\3\E \4\L\5"

"ASDF": "Tuojkldsf",
"ASDF": "Tuojkldsf Fugzihojkowg",
"ASDF": "TUOJKLDSF_FUGZIHOJKOWG_IKEH",
"ASDF": "TUOJKLDSF_FUGZIHOJKOWG_IKEH_SDF",

This worked OK, but I'm trying to accomplish this with a single command?

CodePudding user response:

One way would be to first change the underscores to spaces and then run a normal mode command on all lines:

:%s/_/ /g | %norm f:gu$0Wl~W.W.W.W.W.
  • :%s/_/ /g replace each underscore with a space for every line.
  • | apply another command.
  • :%norm apply the following normal mode commands to each line.
  • f: move cursor to colon.
  • gu$ lower case the rest of the line.
  • 0Wl move cursor to start of values (just inside quoted part).
  • ~ make first letter lower case.
  • W. move to next word (after whitespace) and repeat the lower case command.

As long as you enter enough W. it will give you the output you wanted (since with the :%norm the W motion won't move onto the next line).

Result:

"ASDF": "Tuojkldsf",
"ASDF": "Tuojkldsf Fugzihojkowg",
"ASDF": "Tuojkldsf Fugzihojkowg Ikeh",
"ASDF": "Tuojkldsf Fugzihojkowg Ikeh Sdf",

CodePudding user response:

Vim can handle case in regular expressions. \U matches uppercase, and \L matches lowercase. You also can use these to change case when substituting.

To do this on one line, using the | "another command" pipe, I would approach it like this:

:%s/_/ /g |
Whole file, substitute "_" with " ", all occurences;   more command

%s/\(\a\)\(\a*\)/\1\L\2/g |
%s/             /      /g |  Whole file, substitute, all occurences;   more
   \(\a\)                    Backref capture 1 (first letter of a word)
         \(\a*\)             Backref capture 2 (remaining letters of word)
                 \1\L\2      Backref 1; lowercase the next; backref 2

This would leave us with  Asdf  at the front, but we want  ASDF  so:
%s/^\("\a\{4\}": \)/\U\1/
%s/                /    /    Whole file, subst. (no "g"; should be 1 per line)
   ^                         Start of line
    \("\a\{4\}": \)          Backref is quote, 4 letters, quote, colon, space
                    \U\1     Uppercase the next; backref 1    

All together, on one line: :%s/_/ /g|%s/\(\a\)\(\a*\)/\1\L\2/g|%s/^\("\a\{4\}": \)/\U\1/

Other ways are possible -- instead of having the first letter as a separate backreference capture group, you could make the whole line lowercase, then apply uppercase to the first letter of each word; etc.

  • Related