Replace only in lines where match is found - regex

In vim, how can I replace a word only on specific lines where a string is matched? For example, I have
replace here: foo
but not here: foo
replace again here: foo
and I want to replace foo with bar on all lines where the string replace is found, i.e. the output should be
replace here: bar
but not here: foo
replace again here: bar
Inspired by the sed like syntax of vim's search and replace, I'd expected
:/replace/s/foo/bar/
to work, but it does the replacement only on the first line where it matches replace. How can I extend this to the whole document?

You can use g command (global) for changing it in multiple lines:
:g/replace/s/foo/bar/

In order to become proficient with vim, it is essential to understand how to break down your question and build up an answer. You will also have to learn several of vim's basic operations.
For example, once you break down "How do I delete until the end of a line?" into two questions, you realize that is is just one case of "How do I delete a range of characters?" and "How do I move the cursor to the end of the line?". Then, as you learn more change commands and more motion commands, you never have to ask again.
For the current question, you should break it down into "How do I replace a word on a line?" and "How do I execute a command on multiple lines?"
You already know the answer to the first part: :s/foo/bar. (Some people reflexively add /g to replace all matches on the line. "Golf"ers enjoy the game of accomplishing the task in as few characters as possible.)
The answer to the second part is to use the :global command (short form :g). It can be used to execute (almost) any Ex command on all lines matching a pattern. You can also use :g! or :v to execute a command on all lines that do not match a pattern. So the short answer to your original question is
:g/replace/s/foo/bar
Although your suggestion differs by a single character (not counting the optional, trailing /) it is very different. Your initial /replace/ specifies a range.
I think that a lot of people use :g/{pat1}/s/{pat2}/{expr}/ without ever realizing that it can be broken down and that they can use commands other than :s.
Further reading:
:help :global
:help [range]
:help repeat.txt

Related

vim search replace on some specific lines

Vim substitute command :%s/old/new/g will replace all occurrences of old by new. But I want to do this replacement only in lines that do not start with #. I mean in my file there are some lines starting with # (called commented lines) which I want to exclude in search replace. Is there any way to do it?
You should mix it with the g (see help :global) command:
:%g/^[^#]/s/old/new/
:v/^#/s/old/new
should do the job. check :h :v for details.
The Answer from Thomas is fine. Another approach would be the negative lookbehind:
:%s/\(#.*\)\#<!old/new/g
These method matches every old not preceded by a # while the method of Thomas matches every line not starting with # and then matches every old on that line.
The solution of Kent is a little bit more elegant. It uses :v which is the same as :g!. It matches every line starting with # and then runs the command on every other line. This has the advantage that the regex gets easier. In your usecase the regex to not match it is quite easy, but often it is way easier to build a regex to match a criteria than one that doesn't match. so the :v command helps here.
In your case the global (both of the other answers are using global) command seems safer, since it only checks for a # at the beginning of the line. But depending on the usecase all of the three methods have their advantages

how can I repeat a substitution in vim an arbitrary number of times?

By way of example, I really wish that the following vim substitution command would replace each and every multiple of four leading spaces with a tab in one swell foop
:%s/\v^(\t*) {4}/\1\t/g
But I still have to hammer on #: repeatedly until I get "pattern not found". Is there an elegant way to specify "repeat until pattern not found" on the substitution command? NOTE: I am NOT looking for an an answer that involves using retab -- I want a general purpose solution that is not specific to the pattern (and preferably does not involve writing any kind of script) -- a one-liner that I can rattle off using muscle memory.
You have anchored you substitution with ^. Because of how Vim does substitution's it will look for the next match from the end of the last match. Use a look-behind instead:
:%s/\v(^\s*)#<= {4}/\t/g
For more help see:
:h /\#<=
While not necessarily elegant, it is possible to precede #: with a count. For instance:
1000#:

Vim: Incsearch for replace queries

I noticed I could use regex functions with search in vim, and I could see hilights while I typed by setting incsearch. But that didn't work for search and replace queries like this one:
:%s/std::regex\s\([_a-zA-Z]*\)(/regex_t \1 = dregc(/gc
That one surprised me when it actually worked.
Are there settings or plugins for vim that, like incsearch but better, will highlight your replace query as you type? Just highlighting the matches would be pretty neat, but putting the old and new strings next to eachother in different highlighting colors would be a godsend, because I might not be sure about the backreference.
Not a direct answer to your question, but traditionally in Vim you craft your search regex first, as in:
/regex
Then you hit enter to execute it. The settings :set hlsearch and :set incsearch make this easy to see visually. Then you can just do:
:%s//replace
With no search specified, :%s (substitute acting on %, a shorctut meaning all lines in the file) will use the last search term you specified.
Going one step further, you could then do
:%s/~/replace2
Which replaces your last substitution (in this case, replace1) with replace2.
Unrelated, it may be useful for you to put this in your .vimrc:
set gdefault
Which will make all replaces global by default, so you don't need the /g flag after every :%s command.
You might be looking for vim-over?
This is a plugin that: (to clarify, let's say we're doing :%s/foo/bar/g.) i) highlights matches for substitutions in the buffer (foo) and optionally ii) previews what's after replacement (bar).

Is it possible to use regex for matching with a condition?

I posted this question on super user and I was suggested to post this question on stackoverflow.
I really like vim and today I faced with the intresting problem and I think it can be done via regexp but I can't form out proper one.
I've got a very big sql-file. It consolidates many different queries. File has content with something like this:
select * from hr.employees, oe.orders, oe.order_items
select * from hr.employess, oe.orders, hr.job_history
select * from oe.customers, oe.orders, hr.employees
select * from hr.employees, hr.departments, hr.locations
How can I select only that lines, which has only one match with hr. on the line?. For example above it will be first and third lines.
Sure, it is possible to match such lines. This pattern matches:
^\%(\%(hr\.\)\#!.\)*hr\.\%(\%(hr\.\)\#!.\)*$
Some people like to reduce the amount of backslash-escaping by using the very magic switch \v. Then the same pattern becomes
\v^%(%(hr\.)#!.)*hr\.%(%(hr\.)#!.)*$
(Here I used non-capturing parentheses \%(...\) but capturing parentheses \(...\) would work just as well.)
The question is: What do you want to do with these lines? Delete them?
In that case you could use the :global command:
:g/\v^%(%(hr\.)#!.)*hr\.%(%(hr\.)#!.)*$/d
More information at
:h :global
:h /\v
:h /\%(
:h /\#!
To check if line contains only single occurrence of hr. use regex pattern
^(?=.*\bhr\.)(?!.*\bhr\..*\bhr\.).* with m modifier. I suggest to use grep -P utility.
For that, you need to combine a negative lookbehind with a negative lookahead assertion; i.e. the currently matching pattern must not match before nor afterwards in the same line. In Vim, the atoms for those are \#<! and \#!, respectively.
The pattern to find a single occurrence of X is therefore this:
/\%(X.*\)\#<!X\%(.*X\)\#!/
Applied to your pattern hr.:
/\%(hr\..*\)\#<!hr\.\%(.*hr\.\)\#!/
Problems like this I always find easier to split it into multiple uses of the regex. If I were going to filter through this with grep, I'd do this:
grep "hr\." foo.sql
and that gives me all the lines with "hr.", even the ones with two.
Now I pipe that output through grep again and ask for it to ignore lines with hr. appearing twice:
grep "hr\." foo.sql | grep -v "hr\..*hr\."
I know you're talking about vim, but I'm showing alternatives that might be helpful and might be a good deal clearer.

Vim - sed like labels or replacing only within pattern

On the basis of some html editing I've came up with need for help from some VIM master out there.
I wan't to achieve simple task - I have html file with mangled urls.
Just description Just description
...
Just description
Unfortunately it's not "one url per line".
I am aware of three approaches:
I would like to be able to replace only within '"http://[^"]*"' regex (similar like replace only in matching lines - but this time not whole lines but only matching pattern should be involved)
Or use sed-like labels - I can do this task with sed -e :a -e 's#\("http://[^" ]*\) \([^"]*"\)#\1_\2#g;ta'
Also I know that there is something like "\#<=" but I am non native speaker and vim manual on this is beyond my comprehension.
All help is greatly appreciated.
If possible I would like to know answer on all three problems (as those are pretty interesting and would be helpful in other tasks) but either of those will do.
Re: 1. You can replace recursively by combining vim's "evaluate replacement as an expression" feature (:h :s\=) with the substitute function (:h substitute()):
:%s!"http://[^"]*"!\=substitute(submatch(0), ' ', '_', 'g')!g
Re: 2. I don't know sed so I can't help you with that.
Re: 3. I don't see how \#<= would help here. As for what it does: It's equivalent to Perl's (?<=...) feature, also known as "positive look-behind". You can read it as "if preceded by":
:%s/\%(foo\)\#<=bar/X/g
"Replace bar by X if it's preceded by foo", i.e. turn every foobar into fooX (the \%( ... \) are just for grouping here). In Perl you'd write this as:
s/(?<=foo)bar/X/g;
More examples and explanation can be found in perldoc perlretut.
I think what you want to do is to replace all spaces in your http:// url into _.
To achieve the goal, #melpomene's solution is straightforward. You could try it in your vim.
On the other hand, if you want to simulate your sed line, you could try followings.
:let #s=':%s#\("http://[^" ]*\)\#<= #_#g^M'
^M means Ctrl-V then Enter
then
200#s
this works in same way as your sed line (label, do replace, back to label...) and #<= was used as well.
one problem is, in this way, vim cannot detect when all match-patterns were replaced. Therefore a relative big number (200 in my example) was given. And in the end an error msg "E486: Pattern not found..." shows.
A script is needed to avoid the message.