简体   繁体   中英

How to delete every other line in Vim?

I would like to delete every other line from a Vim buffer, starting with the second one, ie, lines 2, 4, 6, etc. For example, if the buffer's contents is:

aaa
bbb
ccc
ddd
eee
fff

then, after the desired line removal, it should become:

aaa
ccc
eee

Which Vim commands can be used to automate this operation?

完成该任务的一种优雅(且有效)的方法是调用:delete命令(参见:help :d )为+行范围(与.+1相同)寻址当前行之后的行(参见:help {address} ),在每一行(参见:help /^ )使用:global命令(参见:help :g ):

:g/^/+d

You can use a macro for this. Do the following.

  • Start in command mode.
  • Go to the beginning of the file by pressing gg .
  • Press qq .
  • Click arrow down and press dd after.
  • Press q .
  • Press 10000@q

PS: To go to command mode just press Escape a couple of times.

We can use the :normal (or :norm ) command to execute the j and dd Normal-mode commands:

:%norm jdd

Source: the Best of Vim Tips page by zzapper.

:map ^o ddj^o
^o

Here ^ stand for CTRL. Recursive macro to delete a line every two line. Choose well your first line and it's done.

from vim mail archive :

:let i=1 | while i <= line('$') | if (i % 2) | exe i . "delete" | endif | let i += 1 | endwhile

(To be typed on one line on the vim command line, will delete row 1,3,5,7,...)

You can always pipe though a shell command, which means you can use any scripting language you like:

:%!perl -nle 'print if $. % 2'

(or use "unless" instead of "if", depending on which lines you want)

:%!awk -- '++c\%2'

alternatively

:%!awk -- 'c++\%2'

depending on which half you want to weed out.

You can use Vim's own search and substitute capabilities like so: Put your cursor at the first line, and type in normal mode:

:.,/fff/s/\n.*\(\n.*\)/\1/g
  • The .,/fff/ is the range for the substitution. It means "from this line to the line that matches the regex fff (in this case, the last line).
  • s/// is the substitute command. It looks for a regex and replaces every occurrence of it with a string. The g at the end means to repeat the substitution as long as the regex keeps being found.
  • The regex \\n.*\\(\\n.*\\) matches a newline, then a whole line ( .* matches any number of characters except for a newline), then another newline and another line. The parentheses \\( and \\) cause the expression inside them to be recorded, so we can use it later using \\1 .
  • \\1 inserts the grouped newline and the line after it back, because we don't want the next line gone too - we just want the substitution mechanism to pass by it so we don't delete it in the next replacement.

This way you can control the range in which you want the deletion to take place, and you don't have to use any external tool.

As another approach you could also use python if your vim has support for it.

:py import vim; cb = vim.current.buffer; b = cb[:]; cb[:] = b[::2]

b = cb[:] temporarily copies all lines in the current buffer to b . b[::2] gets every second line from the buffer and assigns it to the whole current buffer cb[:] . The copy to b is necessary since buffer objects don't seem to support extended slice syntax.

This is probably not the "vim way", but could be easier to remember if you know python.

To delete odd lines (1, 3, 5, …):

:%s/\(.*\)\n\(.*\)\n/\2\r/g

To delete even lines (2, 4, 6, …):

:%s/\(.*\)\n.*\n/\1\r/g

Search for text (forms the first line) followed by a new line character and some more text (forms the second line) followed by another new line character and replace the above with either first match (odd line) or second match (even line) followed by carriage return.

solution

you can try this in vim

:1,$-1g/^/+1d

edit

I'll try to be more clear (two parts)

first part the selection range ( :from,to )
second part the action ( d delete)

  • the range if from first line to the last but one line ( :1,$-1 )
  • globaly ( g ) delete ( +1d ) next line

you can try to move to the first line of your file and execute :+1d you will see the next line disappear

I know it is muddy, but it's vim and it works

Invoke sed:

:% !sed -e '2~2 d'

^^^^                  pipe file through a shell command
    ^^^^^^            the command is sed, and -e describes an expression as parameter
            ^^^^^     starting with the second line, delete every second line

Responses to comments on another answer on this question (I can't comment because stack overflow requires reputation, it's so bad)

https://stackoverflow.com/a/8334936/16603562

We can use the :normal (or :norm ) command to execute the j and dd Normal-mode commands:

 :%norm jdd

Source: the Best of Vim Tips page by zzapper.


This answer doesn't get enough attention, possibly because of the lack of explanation both here and on the linked, messy, cheat sheet. the norm command runs the normal mode commands equivalent to the letters that follow it. So here it is running j to move down a line, and dd to delete it. This is generalizable to the Xth line by adding more 'j's in this case. – @imoatama May 13, 2014

This doesn't work (try it), but you can delete everything but the Xth line by adding more dd s (or 2dd , replacing 2 with X-1.) Or using the top answer with g , :g/^/+d2 (again replacing 2 with X-1.) Or just use a macro or sed, which also can do the original goal of deleting every Xth line for X>2.

As @Lambart said,

the % is shorthand for 1,$ (execute the following command from line 1 to the end) – Lambart Jul 29, 2014

So if you do %norm jjdd , it will start the counter at line 1, then go to line 3 and delete it, but then it will go to line 2 and then go to line 4 and delete it, etc. This results deleting every odd line except the first line, not every third line.


I'm interested in why %norm ddj doesn't work if you want to delete odd rows. – @Cyker Aug 19, 2018

This is confusing. My guess, based on lots of testing, is that the original %norm jdd works because when something in the command hits a failure, then it stops: if you're at the end and you do jdd , it does j and then stops because it can't go past the end. However, if you do ddj , it deletes first and it stops. But this doesn't stop the %norm , it only stops each line within the %norm .

Also, the counter goes from 1 to the original line number, it doesn't update it. (This makes sense because if it updated, you could do :%norm p and infinite loop.) And if the counter is past the last line of the current file, it goes to the last line. So it deletes all of the lines.

Therefore, to delete every odd line instead of even, you can do %norm jkddj . If it's at the end, it does nothing because j is at the beginning, but if not, it deletes the current line number. But it's easier to either just delete the first line: dd before running the command, or prepend d ( :d|%norm jdd ). It's the same number of characters, but it's more intuitive.

(btw this is tested on Neovim, it's probably the same as Vim but idk I didn't try)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM