1

Preface:

I first encountered this with a Node.js script I was trying to simulate a REPL with. I cooked up this demo to prove that is also happens with bash.

Please explain:

Why is it that on subsequent runs of step 9, you get more than just 5 comments?

Note:

  • The lines that begin with : like are intended to be copy-pasted
  • You can download a copy of the code below via:

curl -O https://gist.githubusercontent.com/RichardBronosky/6f936021ed77e77377729d9ed2d1a1f4/raw/vimception.sh

Code

#!/usr/bin/env bash

echo '##  1. Save this file as vimception.sh                                                                 '
echo '##       :w vimception.sh                                                                              '
echo '##  2. Make it executable                                                                              '
echo '##       :!chmod +x %                                                                                  '
echo '##  3. At the EOF ($), read (r) the result of executing (!) the path (./) to this file (%)             '
echo '##       :$r!./%                                                                                       '
echo '##  4. Run that a few times and watch the comments stack up (colon followed by up arrow is your friend)'
echo "##  5. See the time $(date '+%Y/%m/%d-%H:%M.%S') and [Sub-]Process ID $(sh -c 'echo $PPID') of each run"
      ##  (Okay, enough with echoing the comments!)
      ##  6. Now we'll make this script clean up after itself
      ## 7a. At the EOF ($), put (put) 2 # chars (='##') (This prevents errors by insuring at least 1 match for...)
      ## 7b. separate command (|) globally (g), where pattern (/^##/) matches, delete the line (d)
      ##       :$put ='##'|g/^##/d
      ##  8. Run that a few times to confirm it is idempotent
      ##  9. Combine steps 7 & 3 to get a fresh set of comments rather an seeing them stack up
      ##       :$put ='##'|g/^##/d|$r!./%
      ## 10. Run that one more time
      ## 11. Explain to me what happened
2

I'm not sure to understand, but maybe your issue comes from the global command which doesn't interpret the pipe character as command termination but as a part of its argument. So, when you execute this command:

:$put ='##'|g/^##/d|$r!./%

Maybe you think it does 3 things:

$put ='##'
g/^##/d
$r!./%

But in fact, it does only 2 things:

$put='##'
g/^##/d|$r!./%

In the last command, whenever the global command :g finds a line which begins with 2 number sign characters (#), it executes this command:

d|$r!./%

Which means, it deletes the line and it inserts the output of the shell commands (echo) contained in your current script.

If you don't want :g to interpret the pipe as a part of its argument, you could hide it, wrapping the command in a string and executing it:

:$put ='##'|exe 'g/^##/d'|$r!./%

This time, :g will only see what's inside the string (d), it won't execute your shell script every time it finds a line beginning with ##.


I could be wrong, but I think most Ex commands interpret the pipe as command termination. However there are some exceptions, :g is only one of them. I think the following ones also interpret the pipe as a part of their argument:

:argdo, :bufdo, :tabdo, :windo
:autocmd
:command
:function
:[v]global
:make
:normal
:!

I don't remember where I found this list, maybe in the help or in a blog post. There may be other commands which I missed.

Another way of executing a new Ex command after one of these commands, is to type C-v C-j, which should insert a newline or a null character.

So, you have 2 choices:

:{1st command} C-v C-j {2nd command}

or

:execute '{1st command}' | {2nd command}

Edit: I think I found the complete list of commands, it's in :h :bar:

                            *:bar* *:\bar*
'|' can be used to separate commands, so you can give multiple commands in one
line.  If you want to use '|' in an argument, precede it with '\'.

These commands see the '|' as their argument, and can therefore not be
followed by another Vim command:
    :argdo
    :autocmd
    :bufdo
    :cdo
    :cfdo
    :command
    :cscope
    :debug
    :folddoopen
    :folddoclosed
    :function
    :global
    :help
    :helpfind
    :lcscope
    :ldo
    :lfdo
    :make
    :normal
    :perl
    :perldo
    :promptfind
    :promptrepl
    :pyfile
    :python
    :registers
    :read !
    :scscope
    :sign
    :tcl
    :tcldo
    :tclfile
    :vglobal
    :windo
    :write !
    :[range]!
    a user defined command without the "-bar" argument |:command|
  • Wow! That was exactly it. I even read :help global to know how to explain g/ in the comments. I still had no idea that was the issue. Thanks for the excellent explanation in such a short time! – Bruno Bronosky Jan 12 '17 at 21:18
  • So, for the real use case (node.js) that had me create this, I used $put ='//'|exe 'g?^//?d'|w|r!./% The main difference being the addition of a w for writing the file, and use of // comments. I really like this pattern of editing in a single window rather than monkeying with tmux. – Bruno Bronosky Jan 12 '17 at 21:22
  • @BrunoBronosky Nice! Yes, I think it's a common pitfall in Vim, you have to be aware that not all Ex commands treat the pipe exactly the same way. – user9433424 Jan 12 '17 at 21:32

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.