Vim - Performing substitution on certain lines only - vim

I have found the following in my quest to perform a substitution on even numbered lines only:
:g/^/if !(line('.')%2)|s/foo/bar/g|endif
Works great. But can someone please explain the need for the | characters in the command section of the :g call?

The | character is the command separator; with it, you can concatenate multiple Ex commands in a single line, without adding newlines. See :help :bar.
So, your conditional is equivalent to the following:
if !(line('.')%2)
s/foo/bar/g
endif
Note that some Ex commands consume the entire remainder of the command-line and therefore cannot be directly concatenated with |. But you can wrap those commands in :execute "{cmd}".

Related

Escaping Shell Command Arguments in Vim

I'm trying to fully understand the following command in Vim:
:exe "grep -R " . shellescape(expand("<cWORD>")) . " ."<cr>
I got the use of expand function (force the expansion of into the actual string
before it gets passed to shellescape) and shellescape command itself ( from Vim help page: Escape {string} for use as a shell command argument).
What I do not understand, from help itself either, is that use of dots, one before and one after shellescape command.
Again, both of the dots are preceeded and followed by an empty space.
And if I use :
:exe "grep -R "shellescape(expand("<cWORD>"))" ."<cr>
which is the same command without those dots, I (apparently) get the same result.
Can anybody give a detailed explanation?
Thank you
:help :execute already explains that.
As you can see from the :exe[cute] {expr1} .. syntax, it takes multiple arguments.
Multiple arguments are concatenated, with a space in
between. To avoid the extra space use the "."
operator to concatenate strings into one argument.
:help expr-. explains that the operator for String concatenation in Vimscript is . (not + like in many other languages; in Vimscript, this solely is for adding numbers or Lists). The empty space around it is optional, but often given for better readability.
In summary, if you need to concatenate Strings with spaces, you can either use . and include the space inside one of the Strings, or pass separate arguments to :execute and let it add the spaces implicitly, or mix both approaches within the same command (readability should be the first priority here).

Vim: . (period) repeats the last command. What is the command to repeat the last but one commands?

Vim: . repeats the last command. What's the command to repeat the last but one command?
Ex:
I deleted a line using the command dd, and then I deleted a character using the command x.
x is the last command and dd is the last but one command.
The last command x can be repeated using .
But, I want to repeat dd using something like ..
Short answer: there's no such feature native to Vim.
However, the following suggestions should help you get more out of Vim:
For your specific example, instead of typing dd to delete the line you could use the :d Ex (command-line) delete command. This can be later repeated using #: without effecting the . command.
If you're doing something more complicated than deleting a line with dd, you could record it as a macro using q{registername} (where {registername} denotes any unused non-special register name in the range a-z, e.g., qa). After having executed the macro using #{registername} (e.g., #a) it can be repeated by simply running ##.
If you'd like to learn more about other ways of repeating commands, I'd suggest reading repeat.txt from Vim's built-in manual:
:h repeat.txt
This file lists other ways of repeating commands such as using Ex commands and recording / executing macros.
Repeating substitution commands
The most recent :substitute command can be repeated using :s or :&. This uses the same search pattern and substitution string but not the same flags or range (they have to be provided). An even shorter synonym for this repeat command is &. See :help :substitute for more information.

Exiting exe mode in a macro

I had a large file I was trying to reformat which involved removing the 2nd to nth repeating sets on 2 to 100 lines per duplicate.
The data looked like
element1.element2.element...field.comment
I wanted to remove the repetition in elements after the first instance so of course I went complicated :) and did a macro something like
In a macro Yanked first element on current line to register p and then processed lines yanking the first element into register o and then doing, still in the macro
:if (#p=!#o)|:.s/paste register p//g|else|:norm! j|endif
Now this worked OK except when it got to a line where #p<>#o the :norm! j part stayed in : mode until I manually escaped once or twice then executed the :norm! j command.
I solved the problem an easier way but would like to know why it was only on the else portion that it wouldn't leave :ex mode.
From :help norm
:norm[al][!] {commands} *:norm* *:normal*
...
This command cannot be followed by another command,
since any '|' is considered part of the command.
...
An alternative is to use |:execute|, which uses an
expression as argument. This allows the use of
printable characters to represent special characters.
Example: >
:exe "normal \<c-w>\<c-w>"
So this would do the trick:
:if (#p=!#o)|:.s/paste register p//g|else|:exe "norm j"|endif

Vim: How to delete the same block of text over the whole file

I'm reviewing some logs with Java exception spam. The spam is getting is making it hard to see the other errors.
Is is possible in vim to select a block of text, using visual mode. Delete that block every place it occurs in the file.
If vim can't do it, I know silly question, vim can do everything. What other Unix tools might do it?
Sounds like you are looking for the :global command
:g/pattern/d
The :global command takes the form :g/{pat}/{cmd}. Read it as: run command, {cmd}, on every line matching pattern, {pat}.
You can even supply a range to the :delete (:d for short) command. examples:
:,+3d
:,/end_pattern/d
Put this togehter with the :global command and you can accomplish a bunch. e.g. :g/pat/,/end_pat/d
For more help see:
:h :g
:h :d
:h :range
Vim
To delete all matching lines:
:g/regex/d
To only delete the matches themselves:
:%s/regex//g
In either case, you can copy the visual selection to the command line by yanking it and then inserting it with <C-r>". For example, if your cursor (|) is positioned as follows:
hello wo|rld
Then you can select world with viw, yank the selection with y, and then :g/<C-r>"/d.
sed
To delete all matching lines:
$ sed '/regex/d' file
To only delete the matches themselves:
$ sed 's/regex//g' file
grep
To delete all matching lines:
$ grep -v 'regex' file
grep only operates line-wise, so it's not possible to only delete matches within lines.
you can try this in vim
:g/yourText/ d
Based on our discussion in the comments, I guess a "block" means several complete lines. If the first and last lines are distinctive, then the method you gave in the comments should work. (By "distinctive" I mean that there is no danger that these lines occur anywhere else in your log file.)
For simplifications, I would use "ay$ to yank the first line into register a and "by$ to yank the last line into register b instead of using Visual mode. (I was going to suggest "ayy and "byy, but that wold capture the newlines)
To be on the safe side, I would anchor the patterns: /^{text}$/ just in case the log file contains a line like "Note that {text} marks the start of the Java exception." On the command line, I would use <C-R>a and <C-R>b to paste in the contents of the two registers, as you suggested.
:g/^<C-R>a$/,/^<C-R>b$/d
What if the yanked text includes characters with special meaning for search patterns? To be on the really safe side, I would use the \V (very non-magic) modifier and escape any slashes and backslashes:
:g/\V\^<C-R>=escape(#a, '/\')<CR>\$/,/\V\^<C-R>=escape(#b, '/\')<CR>\$/d
Note that <C-R>= puts you on a fresh command line, and you return to the main one with <CR>.
It is too bad that \V was not available when matchit was written. It has to deal with text from the buffer in a search pattern, much like this.

More detailed advice on advanced mapping

I've found substantial use for commands that will find differences in two lists of words, and the best (and really only) solution to this I've found is this command:
g/^/kl |if search('^'.escape(getline('.'),'\.*[]^$/').'$','bW') |'ld
I want to make a mapping in my _vimrc file that will execute this line whenever I hit F2 for example, but I haven't been able to make it work.
If someone could explain character by character what this line actually does, I think it would make all the difference in the world. I've seen a dozen or so articles on vim mapping and none of them explain what things like / or ^ or \.*{}^$/' do in contexts like this one.
When are : needed in mappings? I've seen some examples with and most without.
When is <CR> needed?
Thanks in advance
This sounds like a job for Awk
:%!awk '!a[$0]{a[$0]=1;print}'
However you asking a two questions:
What does :g/^/kl |if search('^'.escape(getline('.'),'\.*[]^$/').'$','bW') |'ld do?
How can I make a mapping to this?
The Mapping
Let's start with "How can I make a mapping to this?":
:nnoremap <f2> :g/^/kl<bar>if search('^'.escape(getline('.'),'\.*[]^$/').'$','bW')<bar>'ld<cr>
The trick is to use <bar> instead of | and to actually execute the command with <cr>. See :h keycodes.
What does this do?
It goes over every line of the buffer with :g/^/ and deletes lines that are the same as a line from above, if search('^'.escape(getline('.'),'\.*[]^$/').'$','bW') and d. The confusing parts to me are the following:
Using marks needlessly i.e. :k and range with the :d command.
A complicated building of the regex for the search() function. By using the \V (very nomagic) we can reduce the line noise in the regex: '\V\^'.escape(getline('.'),'\').'\$'
Why are you doing an O(N^2) operation when you can do an O(N)?
Simplify the command
g/^/if search('\V\^'.escape(getline('.'),'\').'\$','bWn') | d | endif
We remove the needless marks and simplify the escaping. I also added the endif to show the end of the if statement (this can be optionally left off because it will be assumed).
:g/{pat}/{cmd} The :global command runs {cmd} on ever line matching {pat}
:g/^/ is a common idiom to run a command on every line, since all lins have a beginning, ^.
if {expr} | {cmds} | endif. Execute {cmds} if the expression, {expr}, evaluates to true
search({pat}, {flags}) search for {pat} in the buffer. {flag} alter the search behavior
search() returns the line number of a match or 0 for no match found
b flag for search() means search backwards
W flag means do not wrap around the buffer when searching
n do not move the cursor
escape({str}, {chars}) escape {chars} with \
\V pattern uses very no magic meaning all regex meta characters need to be escaped
\^ and \$ are escaped for start and end of line because of \V option
:delete or :d for short delete the current line
I suggest you use the awk solution at the start of this answer.
:nnoremap <f2> :%!awk '!a[$0]{a[$0]=1;print}'<cr>
For more help see the following:
:h :range!
:h :g
:h :d
:h :l
:h :if
:h search(
:h escape(
:h /\V

Resources