I am using latest available version of NeoVim v0.4.3 and latest Arch build with kernel 5.6.4-arch1-1.

I'm having a problem with the following command in the NeoVim command line:

        1             2              3              4              5               6
:g/^$/d   |   %s/\t//g   |   %s/  / /g   |   %s/$/,/   |   normal gg   |   normal 8J

I have introduced spaces between the bar | so you can see the sequence more clearly.

Note: on the command line, when I run this, the spaces are not there, so it run's Ok in that respect.

Here is what i want to do:

  1. delete all empty lines. This first bit works as it should.
  2. delete all tabs in doc. This second bit works as it should.
  3. replace all double spaces with single space. This bit works as it should.
  4. place one comma "," at end of each line. The command introduces lots of commas at the end of each line, even though I removed the g flag from the :s command. I only want one comma at the end of each line.
  5. go to the top of the file with the normal command. This bit works fine.
  6. join the first 100 lines in the file with 8J. This bit causes the text to become corrupted, and does not appear to join the lines. Ctrl +L does not fix it.

So parts 4 and 6 don't do what I want them to.

Here is the starting text:

the dog

 got up as the
brown fox passed over

this line  ...  too ... and
another line too

one_more   some more 


more information

Here is the text after the command sequence has run up until and including step 4, ie after the end of line $ is replaced with ,:

the dog,,,,,,,
 got up as the,,,,,,,
brown fox passed over,,,,,,,
this line ... too ... and,,,,,,,
another line too,,,,,,,
one_more some more ,,,,,,,
more information,,,,,,,

Here is the text if I run all 6 steps of the command as one, as outlined above, from the beginning:

the dog,,,,,,,
    rmal 8J
  rmal 8J,
 rmal 8J,,
 rmal 8J,,,
 rmal 8J,,,,
 rmal 8J,,,,,
 rmal 8J,,,,,,
 got up as the,,,,,,,
brown fox passed over,,,,,,,
this line ... too ... and,,,,,,,
another line too,,,,,,,
one_more some more ,,,,,,,
more information,,,,,,,

How do I get the command in step 4 to parse each line only once and place only one comma at the end of each line?

How do I get the command in step 6 to join all the lines by running J 8 times?

3 Answers 3


So here is a solution I would use (from my tests it seems to work fine):

execute '%s/\t//g|%s/  / /g|%s/$/,/|g/^,$/d' | norm! gg8J

The issue with your one liner is that vim gets confused because g|pattern|command| is a valid command then thus it doesn't make the difference between your different chained commands and your global command.

So my solution use execute to group the different substitutions and then groups the two normal commands in only one.

Note that for this substitution command %s/ / /g I think you might prefer %s/\s\+/ /g to make sure you handle the 3 whitespaces between one_more and some more.

  • 1
    Your use of %s/\s\+/ /g to make sure 2+ whitespaces are replaced with one is a sound idea.
    – Kes
    May 13, 2020 at 13:28
  • I didn't realise g causes vim to treat chained commands as one command. I see what you did with the execute and the ' ' apostrophes. That is really useful to know.
    – Kes
    May 13, 2020 at 13:30
  • 1
    @Kes That actually reminds me the first question I asked on this site: :g and :s can use almost any non alphabetical character as delimiters and that can be confusing sometimes :) Anyway it's great if that was helpful to you. Also once you can do it, don't forget to mark one of the answers you got as accepted: This is useful to help future readers to know if one solution worked for you.
    – statox
    May 13, 2020 at 14:08

Building long command lines is both complicated, bad style and strongly discouraged for novices. You made a ton of mistakes.

1) :g takes bar as part of argument. Therefore, it must stand on its own line, or, at least, move into "execute": execute 'g/^$/d' | ...

2) Multiple :s can be glued by bar, however you must ensure they couldn't fail, otherwise the remaining part will be skipped. So you must use /e flag: %s/\t//ge | %s/ / /ge | %s/$/,/e | ...

3) normal (really should be normal! instead) also consumes bars (and quotes). Therefore you must put it inside execute again: execute 'normal! gg' | .... But you may also consider to change it into something else. For example, 1 is a valid command to go to the line number one. And, in fact, gg8J is an equivalent of 1,8join.

So it could be something like

execute 'g/^$/d' | %s/\t//ge | %s/  / /ge | %s/$/,/e | %join
  • thanks I didn't know about 1) :g taking bar as part of the argument, or 2) that /e will allow use in case previous command fails. Are you saying that use of /e unglues the s gluing? I notice you did not use normal! but instead %join which joins everything which works here! I did'nt find I could get normal commands to work in there without the prefix normal. Going to look at that again
    – Kes
    May 13, 2020 at 13:24
  • "gg8J is an equivalent of 1,8join". I realised that as I read your comment. But woud I have remembered it otherwise? With a small prompt, yes, perhaps. I am a vim newbie and this is an incredibly dexterous tool/ language. Why I spent years wasting time using notepad plus, geany etc, who knows .. Vim / sed / regex etc should be taught to every kid at school.
    – Kes
    May 13, 2020 at 13:42
  • 1
    @Kes /e flag prevents :substitute from throwing an error if no match was found (which would prevent next :s from run, see :h :bar)
    – Matt
    May 13, 2020 at 14:30

The problem you're having here is that some commands take the | and use it, which makes the | not usable as a command separator anymore (at least not for that particular command and the ones that follow.)

See :help :bar:

These commands see the '|' as their argument, and can therefore not be followed by another Vim command:

  • :global (:g)
  • :normal
  • ...

Having :g in that list means that what's actually happening here is that you're matching g/^$/ on the empty lines, then running all of d | %s/\t//g | %s/ / /g | %s/$/,/ | normal gg | normal 8J on those lines!

Having :normal in that list means that the Normal-mode command you're executing is actually gg | normal 8J, which explains why at some point you execute the o as a command entering Insert mode on the next line, then interpreting rmal 8J as text to insert.

One simple solution to this would be to avoid using | altogether and simply enter each command on a separate line.

Another possible solution is to use :execute to wrap a command like :g to prevent it from "swallowing" the separators. For the two normal commands, you can simply join them into normal gg8J since running two :normals in a row should be equivalent to the concatenation of them.

Putting it all together:

:exe 'g/^$/d' | %s/\t//g | %s/ / /g | %s/$/,/ | normal gg8J
  • 2
    Damn it seven seconds of difference and one quote placed differently, great minds think alike ;)
    – statox
    May 13, 2020 at 10:18
  • 2
    @statox Nice idea of moving the :g to the end! And very nice that you actually suggested improvements to the regexes too. I mostly tried to address the issue with the delimiters...
    – filbranden
    May 13, 2020 at 10:24
  • 2
    Well this is the reason why it is always better to have several answers to one questions: people think differently, they explain stuff differently and they don't focus on the same topics. So it's cool to have answers which complements each others :)
    – statox
    May 13, 2020 at 11:43
  • 1
    @filbranden, thanks. I had a look at :help :bar and it's clear to me now why this did not work, and that as you say, :global or :g takes | as a an argument, and therefore | cannot be followed by another vim command.
    – Kes
    May 13, 2020 at 13:54
  • 1
    @filbranden "Having :g in that list means that what's actually happening here is that you're matching g/^$/ on the empty lines, then running all of d | %s/\t//g | %s/ / /g | %s/$/,/ | normal gg | normal 8J on those lines!" That is very insightful to me. Would'nt have seen that otherwise !
    – Kes
    May 13, 2020 at 13:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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