Search and replace in vim in specific lines - vim

I can use
:5,12s/foo/bar/g
to search for foo and replace it by bar between lines 5 and 12. How can I do that only in line 5 and 12 (and not in the lines in between)?

Vim has special regular expression atoms that match in certain lines, columns, etc.; you can use them (possibly in addition to the range) to limit the matches:
:5,12s/\(\%5l\|\%12l\)foo/bar/g
See :help /\%l

You can do the substitution on line 5 and repeat it with minimal effort on line 12:
:5s/foo/bar
:12&
As pointed out by Ingo, :& forgets your flags. Since you are using /g, the correct command would be :&&:
:5s/foo/bar/g
:12&&
See :help :& and friends.

You could always add a c to the end. This will ask for confirmation for each and every match.
:5,12s/foo/bar/gc

Interesting question. Seems like there's only range selection and no multiple line selection:
http://vim.wikia.com/wiki/Ranges
However, if you have something special on line 5 and 12, you could use the :g operator. If your file looks like this (numbers only for reference):
1 line one
2 line one
3 line one
4 line one
5 enil one
6 line one
7 line one
8 line one
9 line one
10 line one
11 line one
12 enil one
And you want to replace one by eno on the lines where there's enil instead of line:
:g/enil/s/one/eno/

You could use ed - a line oriented text editor with similar commands to vi and vim. It probably predates vi and vim.
In a script (using a here document which processes input till the EndCommand marker) it would look like:
ed file <<EndCommands
5
s/foo/bar/g
7
s/foo/bar/g
wq
EndCommands
Obviously, the ed commands can be used on the command line also.

Related

A custom function in Vim

I'm quite new to Vim/Vi, and I need to write a custom function/macro.
Is it possible to define a command (ex. :mycommand) that would format the lines in the current file like so:
Initial lines:
This is line 1
This is line 2
This is line 3
This is line 4
This is line 5
This is line 6
This is line 7
This is line 8
Result:
This is line 1\nThis is line 2
This is line 3\nThis is line 4
This is line 5
This is line 6\nThis is line 7\nThis is line 8
How do I go about creating such a script? And where do I place it?
Marco
You can record a macro, to join two lines by \n separator, for example in your vimrc:
let #x='gJi\n^['
(the ^[ above, you press ctrl-v Esc)
Assume your cursor is on the first line, you can do #x in normal mode, then )J## for the 2nd block, then job is done.
You can wrap those operations in function or create them as mapping.
Join plugin
I have written a Join script: https://github.com/sk1418/Join , it supports to join lines with separator and other features, in your case, you can execute command: :J '\n', it will do what above macro (#x) does. You can put it in your function too, like:
function Foo()
Join '\n'
join!
Join '\n'
endfunction
Update for the Question modification:
The modification you made, turned the question into another one... However it could be solved, I listed two possibilities below, one is vim way, the other is with external awk tool, if you have awk available on your system.
with vim :s cmd
This command should do it for you:
%s/\n\n/∢/g|%s/\n\ze./\\n/g|%s/∢/\r/g
the ∢ is done by pressing ctrl-v u2222, it is just for a special char, which not exists in your text, you can use other uni-code chars too.
with external awk
%!awk -v RS='\n\n' '{gsub(ORS, "\\n")}7'
This will do the transformation for you, however it leaves an extra \n at the end of the file, just remove it.

How to express the line before the last in vim?

How to express the line before the last in vim?
It is clear that $ is the last line.If there is a article which contain 20 llines,$ is equal 20 here, how about the line before the last,it is 19 ,how to write it such as $-1 ,can we create a expression 19=$-1??
If you want to reference the last minus n line in command line you can use $-n.
For instance, to go to the second to last line:
:$- # same as :$-1
You can use it to define a range:
:,$-10 # from the current line to 10 lines before the last one
It is a bit unclear what you are asking.
A solution to reach the penultimate line is using something like :
:normal Gk
that you can map in a user defined command.

How to replace current string line with another line in vim?

I'd like to replace current string line with another (for example the another line is placed in 5 lines above current line). I can do it with a pair of commands
dd
:-5t-1
Is there the shorter way to obtain same goal?
dd
:-5t-1
is already pretty short if you ask me. But you can squeeze everything into a one-liner:
:d|-5t-1
and remove the 1 because it's implied by -:
:d|-5t-
Barring making a custom command or mapping I don't see how you could make it shorter.
:-5y<CR>Vp
is it shorter?
if you need do that really often, add this into your vimrc:
command! -range R d|<line1>,<line2>t-
then you can just do :-5R replace current line with -5 line
or 2,4R to cp line 2-4 (3 lines) to current line, and replace current line.
If you don't mind a plugin, my LineJuggler plugin offers a ]r command (and many more):
]r Fetch the line [count] visible lines above the current line and replace the current line with it.
With it, your example would be the short and easy 5]r
In addition, the companion LineJugglerCommands plugin now offers a similar :Replace Ex command. Again, your example would be
:Replace -5

VIM - explain 'normal' and 'global' commands

Can somebody clarify me how global (:h :g) and norm (:h norm) commands are working in VIM? I have file:
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
G
H
I have issued :g/[0-9]/norm 4gg dd hoping that it will work in following maner:
[0-9] = match only lines with numbers
4gg = jump to 4th line
dd = delete current (4th) line
So I was expecting this:
1
2
3
5
6
7
8
9
A
B
C
D
E
F
G
H
But instead of it I get:
1
2
3
4
A
B
C
D
E
F
G
H
Also it does not matter if I use norm or norm!, what is the difference, can you please explain me how this is working or point me to some good references, I have read :h :g and :h :norm but it does not help. Thank you
PS: I can use :4d but I am interested in :g and :norm explanation, the problem was mentioned just as simple example.
:g/pattern/do something
will do do something on each line that matches the pattern. so you got that output.
try:
:g/\d/echo getline('.')
:g/\d/echo line('.')
1st line prints the matched line
2nd prints the matched line number.
so you will see for each matched line, vim does something (echo, in this case)
and for the reason, why 4 is till there, because you have a space between 4gg and dd.
Your line with number has only one char, so space will move cursor to next line. that's why 4 won't be deleted.
remove the space!
:normal
:{range}normal {command}
executes normal mode {command} on every line in {range}. Without {range}, it operates on the current line.
Example:
:5,18normal 0df=
:global
:{range}g/{pattern}/{command}
executes {command} only on lines in {range} that match {pattern}. Without {range}, it operates on the whole buffer.
Examples:
:5,18g/^foo/normal 0df=
:5,18g/^foo/s/^.\{-}=/
It means:
For every line that contains any digit between 0 and 9, go to fourth line and delete it. So in first iteration it deletes the number 4, in the next one the number 5 and so on. When cursor begins with lines that don't contain number, it doesn't match and doesn't execute the normal instruction, so it doesn't delete anything.
To complete your questions (note that it is bad style to ask several questions (unless tightly related), and you should have been able to resolve most of that through the excellent :help):
The difference between :normal and :normal! is that the former considers mappings, whereas the latter always works on the default Vim commands. Therefore, the former is okay for ad-hoc commands (that make use of your mappings), but the latter is recommended for plugins (to be independent of any mappings).

How to let Vim process my command backforward?

Suppose I have 5 lines of text, if I input some commands to let vim process each line, Vim will process the text one by one, first line, second line, ... the last line. what I want is to let Vim process my text in reverse order. that is the last line first, then the 4th line, and at last the first line.
Why I need this?
I have the following text
1234567890
abc
123
def
1234567890
123456789
I want to remove the newline symbol(\n) from lines which contains 3 characters. so after processing,I will get the following text
1234567890
abc123def1234567890
123456789
It seems a piece of cake, I use the following command
:%s/\v(^\w{3})\n/\1/
But what i got is
1234567890
abc123
def1234567890
1234567890
Why? I guess vim first remove \n from the second line, then this line has text abc123, now vim will not remove \n after 123, since it's not 3 characters now, so vim process the next line def, and remove \n from it, that's the result i got.
If vim can process from back to front, this will not happen and I can got the result I want.
BTW, I can get the expected result in other ways, I just want to know whether this is possible.
Explicitly loop over the range of lines (e.g. the visually selected ones) backwards, then execute the command on each line. I've used :join here instead of the :substitute with a newline:
:for i in range(line("'>"), line("'<"), -1)| silent execute i . 'g/^\w\{3}$/join!' | endfor
Can be achieved using perl:
while (<>) {
chomp if (/^...$/);
print;
}
In this case it is easier to use the :global command to join the lines.
:g/^\w\{3}$/normal! gJ
The command gJ joins the current line with the following line without inserting any spaces. The global command above calls gJ on each line containing only three characters. It works by marking all the matches first, before performing the operation, so the problem of looping is avoided.
this line should do what you want:
:%s/\v(\_^\w{3}\n)+/\=substitute(submatch(0),"\n","","g")/
if you want to do it simpler with external command, e.g. awk, you could:
%!awk '{printf "\%s", length($0)==3? $0:$0"\n"}'

Resources