5

I am attempting to delete from start of line until string foo. In normal mode, 0d/foo accomplishes this. Now, I would like to do this for every line in my buffer. Reading documentation and SO it seems that %normal command is a good match for this; indeed:

:%norm! 0dfo

deletes from the start of line to the first inclusive occurrence of char o, as expected. However:

:%norm! 0d/foo

has no effect in Vim 8.0. Oddly enough, it works as I expected in Emacs EVIL mode. Am I misunderstanding the expected behavior?

4 Answers 4

6

It is expected. :norm! sequence is roughly equivalent to pressing the sequence followed by escape. Try typing 0d/foo<esc>, you'll see it doesn't work because escape terminates the motion and operator (by default with nocompatible). Instead you should use :%norm! 0d/foo^M, where ^M is a literal carriage return character (type CTRL-V CTRL-M or CTRL-M enter).

3

There is a problem with your search motion approach. If "foo" doesn't appear on a line or multiple consecutive lines, you will end up deleting entire lines up until the next instance of "foo"!

Instead of using a search motion, you could simply use a global :substitute and leverage the zero-width atom \ze:

:%s/^.\{-}\zefoo//

This will delete any and all text from the beginning of a line up to, but not including "foo", for every line in the buffer. The \{-} atom is a non-greedy version of *, meaning it will match as few times as possible, deleting only up to the first "foo" on the line (Thanks for catching this, wmmso).

Since you didn't specify in your question, if you want to match only on the whole word "foo" and not substrings, then you must wrap "foo" in \< and \> like so:

:%s/^.\{-}\ze\<foo\>//

How it works

\ze is a zero-width atom that explicitly marks the end of the match. In other words, it overrides where the actual end of a match is, even if there is more matching text after the \ze, like in the command above.

Here is how the help page describes it, from :h \ze:

Matches at any position, and sets the end of the match there: The previous char is the last char of the whole match. Can be used multiple times, the last one encountered in a matching branch is used.

Example: "end\ze(if\|for)" matches the "end" in "endif" and "endfor".

This is a pretty good explanation, but I think an interactive, visual example speaks for itself. Let's look at this example buffer:

cats dogs birds foo fish
cats foo dogs birds fish

If you don't have it enabled already, :set hlsearch incsearch. Now, begin a search like so: /^.\{-}foo. Hitting Enter is unnecessary. The buffer should visually highlight the exact matched portion like so (assume the brackets denote what is highlighted):

[cats dogs birds foo] fish
cats foo dogs birds fish

As expected, this matches everything from the beginning of the line, stopping with "foo". But we don't want to include "foo" when we delete, so how do we keep "foo" out of the match, while still requiring its presence? \ze to the rescue.

Assuming you still have the search open, modify it by inserting a \ze between the * and foo, just like in the pattern in the top of this answer: /^.\{-}\zefoo. You should see the highlighted portion shrink back to just before "foo":

[cats dogs birds ]foo fish
cats foo dogs birds fish

It is important to remember that "foo" is still an important part of this match in that it must be there for anything to match at all. If we replace "foo" with "bar" in the example buffer, the same pattern would return not return any matches:

cats dogs birds bar fish
cats bar dogs birds fish

Bonus Information

Just as an extra bit of info, \zs is the counterpart to \ze, and sets the start of the match. This can come in handy, for example, when what you'd like to match is preceded by whitespace that you don't want in the match. /\s*\zsfoo would match only "foo", even if it had an arbitrary amount of whitespace before it.

6
  • 1
    You can fix the problem with "foo", not being on the line with :g, e.g. :g/foo/norm d//^m Mar 30, 2018 at 14:07
  • 1
    If there's more than one "foo" on the line, you'll want to use a non-greedy search: :%s/^.\{-}\zefoo//
    – Rich
    Apr 6, 2018 at 9:24
  • @Rich Right you are, good catch.
    – ZeroKnight
    Apr 6, 2018 at 9:32
  • Credit should go to @wmmso for spotting it: they mentioned it in their answer.
    – Rich
    Apr 6, 2018 at 9:36
  • @PeterRincker, what does the ^m do?
    – alpha_989
    Apr 6, 2018 at 12:20
0

I use the global command a lot if I have to repeat a normal mode command over a text file such as

%g/foo/norm ^d$

This will search for lines which contain the word foo, on all lines in the current buffer. Then it will go to the first character of the line, and delete everything till the end of the line

4
  • 1
    OP wants to delete up to the "foo", not to the end of the line. See @PeterRincker's comment. Also I believe the ^ is unnecessary. It's not documented as such in :h :g, but :h :normal-range explains that the latter command positions the cursor at the start of the line before running the normal command(s), and :g//norm has always worked that way for me, too.
    – Rich
    Apr 6, 2018 at 9:35
  • 1
    Yes. you are right.. about both points. I havent tested this but this will work instead then. %g/foo/norm d/foo. This will search search for the lines containing foo, then apply the normal mode command. Since it only selects the lines containing foo, it will only delete the text till till foo on each of the lines containing foo.
    – alpha_989
    Apr 6, 2018 at 12:14
  • 1
    You need to add the ^M at the end (See the accepted answer for why). (I think this is what :h :normal means when it says: A ":" command must be completed as well.) And note that you can shorten this by reusing the search pattern. Then you basically have the same command that Peter Rincker suggested :).
    – Rich
    Apr 6, 2018 at 15:11
  • Interesting.. Didn't know about this thank you for pointing it out..
    – alpha_989
    Apr 6, 2018 at 16:28
0

Using this as test text in a plain vanilla vim 7.4 set with 'nocompatible':

first
 food is good.   foo 123 foo 123 foo 123
qwerty qwer yui
   foo 123 foo 123 foo 123
last

tl;dr

  1. Record the following into a register using, eg., qa:

    /\<foo\>^Md0j
    

finish up with <ESC> (terminate recording)

  1. Type

    :% :normal @a
    
  2. hit enter, voila done

The nitty gritty, or, how I got it to work.

d/foo at the beginning of a line which has "foo" in it works great. But using it on a line without "foo" will delete all the lines between here and there. Using :g one can get a command to work on each line containing foo:

Using :g/foo/ s/foo/FOO/, you can get the first foo on each line to turn into FOO. So, you'd THINK using

:g/foo/ d/foo or :g/foo/ d/foo/

would be the ticket. Nope. Results in a "trailing characters" error.

g/foo/ :normal d2fo works but if "o" appears twice before "foo", this won't have the desired effect.

Searching for "/foo/e" will move the cursor to the end of a search pattern. And moving to a line where "foo" exists and entering

:%normal d/foo/e

will indeed delete to the end of "foo". So, again, you'd THINK

:%normal d/foo/e

would just work. Nope. Does nothing.

Using :%s/.*foo// again kills all foos. Not what the OP asked. and using \ze gets non-greedy behavior, but it's overridden by earlier matches on the line - see :h non-greedy. And, they're right. .*foo matches before ^-to-the-first-foo, so it wins. Maybe I just don't understand using \ze.

SO...

Putting cmds in a register and using @-register-name works, so let's try d/foo^M in a register q and use :% @q. Nope.

Cutting a long story short - let's record into register "q" what it takes to do this on a single line: put

/\<foo\>^Md0j

into register (eg) q or use qq and type it using ^V^M where ^M appears. Or use qq to record it.

Now execute q on each line using

:% :normal @q

happiness.

dissecting

/\<foo\>^Md0j

the "/foo" finds the next occurrence - the < and > limit the search to just "foo", not "food". ^M should have been entered in record mode as hitting enter. d0 deletes to column 1. v0x works too. j gets to the beginning of the next line, preventing finding another "foo" on this line.

Rube Goldberg, where are you...

5
  • 1
    :g/foo/ d/foo is applying the ex command :delete, not the normal mode command d. The :delete command doesn't take any arguments, so you get the "trailing characters" error because of the /foo that follows it. The way to use a normal mode command with :g is via :normal: :g/foo/norm d//^M (as @PeterRincker suggested above
    – Rich
    Apr 6, 2018 at 9:19
  • <Esc> doesn't terminate a macro recording. For that you need to use q
    – Rich
    Apr 6, 2018 at 9:20
  • You've got the command :%normal d/foo/e written twice in a row, and you're saying that the first one works, but the second doesn't. I'm presuming one of those is a copy/paste error?
    – Rich
    Apr 6, 2018 at 9:22
  • 1
    You're correct about @ZeroKnight's answer using a greedy search when we want non-greedy, but it's easy to fix: :%s/^.\{-}\zefoo//
    – Rich
    Apr 6, 2018 at 9:25
  • At the time, my head was stuck on the fact that omitting the g flag from the substitute command would (normally) stop after the first match, so I subconsciously checked out after that, totally missing the greedy effect of the * quantifier. Whoops. :)
    – ZeroKnight
    Apr 6, 2018 at 10:19

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.