How to use regex in vim to toggle case for all characters in a file

Viewed 52

I have a file which has the following text

Lorem IpSuM is SIMPLY duMMy TeXt of the PrINtinG and typesetting industry.
Lorem Ipsum has been the industry's stanDaRd dummy text ever since the 1500s,
whEn an unknown printer took a galley of type and sCrAMBled it to MakE a type specimen BoOk.

After toggling the case of all the characters the text file should look like:

lOREM iPsUm IS simply DUmmY tExT OF THE pRinTINg AND TYPESETTING INDUSTRY.
lOREM iPSUM HAS BEEN THE INDUSTRY'S STANdArD DUMMY TEXT EVER SINCE THE 1500S,
WHeN AN UNKNOWN PRINTER TOOK A GALLEY OF TYPE AND ScRambLED IT TO mAKe A TYPE SPECIMEN bOok.

I am using the following regex pattern:

%s/\<\([A-Z\s\]+\)\>/\L&/

But vi editor is throwing an error:

E486: Pattern not found: \<\([A-Z\s\]+\)\>/\L&/

What is the correct pattern for toggling case of all characters in a file at once using regex in vim?

2 Answers

Why going for regex and :s, if you can do ggShift+vG~?

If you have to do it in a script, you can use :normal:

:normal ggVG~

Well, if you really want to go for :s, you can do this

%s/\a/\=submatch(0) >= 'a' && submatch(0) <= 'z' ? toupper(submatch(0)) : tolower(submatch(0))/g

(I suspect there's a way to write submatch(0) >= 'a' && submatch(0) <= 'z' more concisely, but I don't know of it, or it simply doesn't come to my mind. romainl's answer shows that way.)

%s/\<\([A-Z\s\]+\)\>/\L&/

is not a "pattern", only that part is:

\<\([A-Z\s\]+\)\>

Note that Vim is complaining about this pattern:

\<\([A-Z\s\]+\)\>/\L&/

which means that Vim couldn't spot the separator between the search part and the replacement part. This is likely due to this backslash:

\<\([A-Z\s\]+\)\>/\L&/
          ^

Removing it makes for a proper substitute command but Vim still says it can't find the pattern. Let's deconstruct it to see why…

  • \< and \> mean that you want your pattern to be anchored at the beginning and end of a word.
  • \( and \) delimit a capture group but you don't use one in the replacement so it is unnecessary.
  • + is a literal +, if you want "one or more of the previous atom", you need \+. This is probably where the extraneous backslash comes from.
  • \s means that you want to include a space in your pattern, which is a bit contradictory with \< and \>.

It looks like you are trying to match words made of only uppercase characters, which doesn't make sense given your stated goal.

And then there is the \L in the replacement part, which makes the text lowercase. If you want to toggle casing, turning all uppercase characters into lowercase is useless because then you can't toggle the case of the previously lowercase characters anymore as everything is now lowercase. This is not a good strategy.

Using a normal mode command, as suggested by the other answer, sounds like a much better strategy.

As for substitutions, there is no built-in "toggle" equivalent of \L and \U so you need to beef up your replacement part with a bit of vimscript:

:%s/./\=submatch(0) =~ '\L' ? toupper(submatch(0)) : tolower(submatch(0))

where…

  • \= tells Vim to use the result of an expression as replacement,
  • what follows is a ternary condition where we return the uppercase version of the match if it is lowercase or the lowercase version if it is not.

See :help sub-replace-special, :help sub-replace-expression, :help string-functions.

Related