vim search replace on some specific lines - regex

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

Related

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#:

vimscript E888: (NFA regexp) cannot repeat

I am attempting to revive the cocoa.vim script.
Error detected while processing function objc#man#ShowDoc:
line 32:
E888: (NFA regexp) cannot repeat
Line 32 of the function objc#man#ShowDoc is:
let attrs = split(matchstr(line, '^ \zs*\S*'), '/')[:2]
First, I don't understand the error. What's repeating? What can't it repeat? Searching for that error online brings me to where it's defined in vim's source code, but it's obtuse enough that I don't understand it.
Second, I find it strange that this regexp used to work, but now it doesn't with newer vim.
I have very little vimscript experience and not much regexp experience. Guidance on where to look from those who do would be much appreciated. Here is the whole src if you're interested.
The problem is that \zs is the start of match regex atom. Repeating it zero or more times doesn't make any sense since you can always start the match at the point the \zs is.
This error was introduced vim patch 7.4.421 to stop a crash in vim when the NFA engine tried to generate the regex. Most likely the star shouldn't even be there. The old regex engine allowed it however I don't believe it did anything meaningful.
You should be able to fix it by just removing the star.
let attrs = split(matchstr(line, '^ \zs\S*'), '/')[:2]
(You might also try adding \%#=1 to the regex to force the old engine. You might want to read :h E888 to see if it says anything useful. I don't have a vim version with this patch level to test right now.)
The help for :h \zs is copied below.
/\zs
\zs Matches at any position, and sets the start of the match there: The
next char is the first char of the whole match. /zero-width
Example:
/^\s*\zsif
matches an "if" at the start of a line, ignoring white space.
Can be used multiple times, the last one encountered in a matching
branch is used. Example:
/\(.\{-}\zsFab\)\{3}
Finds the third occurrence of "Fab".
{not in Vi} {not available when compiled without the +syntax feature}

Replace only in lines where match is found

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

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.

Need a regex to exclude certain strings

I'm trying to get a regex that will match:
somefile_1.txt
somefile_2.txt
somefile_{anything}.txt
but not match:
somefile_16.txt
I tried
somefile_[^(16)].txt
with no luck (it includes even the "16" record)
Some regex libraries allow lookahead:
somefile(?!16\.txt$).*?\.txt
Otherwise, you can still use multiple character classes:
somefile([^1].|1[^6]|.|.{3,})\.txt
or, to achieve maximum portability:
somefile([^1].|1[^6]|.|....*)\.txt
[^(16)] means: Match any character but braces, 1, and 6.
The best solution has already been mentioned:
somefile_(?!16\.txt$).*\.txt
This works, and is greedy enough to take anything coming at it on the same line. If you know, however, that you want a valid file name, I'd suggest also limiting invalid characters:
somefile_(?!16)[^?%*:|"<>]*\.txt
If you're working with a regex engine that does not support lookahead, you'll have to consider how to make up that !16. You can split files into two groups, those that start with 1, and aren't followed by 6, and those that start with anything else:
somefile_(1[^6]|[^1]).*\.txt
If you want to allow somefile_16_stuff.txt but NOT somefile_16.txt, these regexes above are not enough. You'll need to set your limit differently:
somefile_(16.|1[^6]|[^1]).*\.txt
Combine this all, and you end up with two possibilities, one which blocks out the single instance (somefile_16.txt), and one which blocks out all families (somefile_16*.txt). I personally think you prefer the first one:
somefile_((16[^?%*:|"<>]|1[^6?%*:|"<>]|[^1?%*:|"<>])[^?%*:|"<>]*|1)\.txt
somefile_((1[^6?%*:|"<>]|[^1?%*:|"<>])[^?%*:|"<>]*|1)\.txt
In the version without removing special characters so it's easier to read:
somefile_((16.|1[^6]|[^1).*|1)\.txt
somefile_((1[^6]|[^1]).*|1)\.txt
To obey strictly to your specification and be picky, you should rather use:
^somefile_(?!16\.txt$).*\.txt$
so that somefile_1666.txt which is {anything} can be matched ;)
but sometimes it is just more readable to use...:
ls | grep -e 'somefile_.*\.txt' | grep -v -e 'somefile_16\.txt'
somefile_(?!16).*\.txt
(?!16) means: Assert that it is impossible to match the regex "16" starting at that position.
Sometimes it's just easier to use two regular expressions. First look for everything you want, then ignore everything you don't. I do this all the time on the command line where I pipe a regex that gets a superset into another regex that ignores stuff I don't want.
If the goal is to get the job done rather than find the perfect regex, consider that approach. It's often much easier to write and understand than a regex that makes use of exotic features.
Without using lookahead
somefile_(|.|[^1].+|10|11|12|13|14|15|17|18|19|.{3,}).txt
Read it like: somefile_ followed by either:
nothing.
one character.
any one character except 1 and followed by any other characters.
three or more characters.
either 10 .. 19 note that 16 has been left out.
and finally followed by .txt.