How to search a match on a line and then make a change on the next line?

122 views
Skip to first unread message

boB Stepp

unread,
May 13, 2020, 11:38:17 PM5/13/20
to Vim Users
I am relatively new to using regex pattern matching techniques to search for and
replace text. Today I have been trying to get a better mastery of these
techniques. But I am currently stumped on the following text search and
replace:

# Home address fields:
# field address STANDARD FIELD -- CANNOT EDIT!
# field address2 STANDARD FIELD -- CANNOT EDIT!
pobox = "PO Box", string
# field city STANDARD FIELD -- CANNOT EDIT!
# field state STANDARD FIELD -- CANNOT EDIT!
# field zip STANDARD FIELD -- CANNOT EDIT!
# field country STANDARD FIELD -- CANNOT EDIT!

# Work address fields:
field work_address = Address, string
field work_address2 = Address2, string
pobox = "PO Box", string
field work_city = City, string
field work_state = State, string
field work_zip = Zipcode, string
field work_country = Country, string

# Other address fields:
field other_address = Address, string
field other_address2 = Address2, string
pobox = "PO Box", string
field other_city = City, string
field other_state = State, string
field other_zip = Zipcode, string
field other_country = Country, string

In the above I want to change the lines starting with "pobox" to match the
format of the line above it, e.g., "field pobox", "field work_pobox" and
"field other_pobox", respectively. My best effort so far to do this is:

:g/\(field [[:alnum:]_]*\)address2/+s/pobox/\1pobox/

My current understanding (flawed though it is) feels that this command
should do the trick, but it doesn't; instead, it highlights the three
instances of "pobox" and says that it has made three changes without
visibly changing anything. It is as if "\1" is not storing anything.
What I _think_ the above command is doing is:

1) Search globally and find each line that has the string "field <one or
more alphanumeric characters or underline>address2".

2) Store the above strings in "\1" which I should be able to use in the
replacement string to follow.

3) Advance to the next line with "+".

4) Search on this line for the string "pobox".

5) Replace that instance with "<contents of "\1">pobox".

What am I misunderstanding?

--
Wishing you only the best,

boB Stepp

Tony Mechelynck

unread,
May 14, 2020, 12:54:37 AM5/14/20
to Vim Users
I would do that with a macro, as follows (starting from Normal mode): (untested)
0. Go to top of file:
gg
1. Start recording into register q:
qq
2. Search for "pobox" as a full word starting in column 1:
/^pobox\><CR>
(where ^ is a real spacing circumflex and <CR> means "Hit Enter")
3. Go to previous line
k
4. Visually mark characterwise until but not including "addr"
v/.\zeaddr
5. Yank the current visual selection (cursor goes back to column 1)
y
6. Go to next line
j
7. Put before
P
8. End recording
q
9. Repeat what we just recorded until we cannot:
9999@q

(Use a higher count if you have more than 10,000 sections in the file.)


Best regards,
Tony.

Tim Chase

unread,
May 14, 2020, 9:00:51 AM5/14/20
to boB Stepp, vim...@googlegroups.com
On 2020-05-13 22:38, boB Stepp wrote:
> I am relatively new to using regex pattern matching techniques to
> search for and replace text. Today I have been trying to get a
> better mastery of these techniques. But I am currently stumped on
> the following text search and replace:
>
> # Home address fields:
> # field address STANDARD FIELD -- CANNOT EDIT!
> # field address2 STANDARD FIELD -- CANNOT EDIT!
> pobox = "PO Box", string


So based on my reading of your description & regex, you want this to
become

field pobox

rather than

# field pobox

(with the leading hash+space)?

> In the above I want to change the lines starting with "pobox" to
> match the format of the line above it, e.g., "field pobox", "field
> work_pobox" and "field other_pobox", respectively. My best effort
> so far to do this is:
>
> :g/\(field [[:alnum:]_]*\)address2/+s/pobox/\1pobox/

I'd go with something like

:%s/\(\<field \+\w*\)address2.*\n\s*\zs\zepobox\>/\1

which captures the "field" and optional prefix, then sets the start &
end of the match with \zs and \ze so that the replacement-capture
gets inserted there.

-tim




boB Stepp

unread,
May 14, 2020, 1:27:23 PM5/14/20
to vim...@googlegroups.com
Thank you, this works. I had to look up some of the flags you used before
I understood what you are doing. In my particular example I found I did
not need to use "\+", "\<" and "\>" to get the desired result. But I suspect that you are using
these to protect against scenarios I am not considering. Would you mind
elaborating on why you included those additional flags?

As I am trying to understand the "why"s of all of this, it would be helpful
for my understanding if someone would explain why my regex did not work as
expected. I am still not seeing it.

Tim Chase

unread,
May 14, 2020, 1:52:46 PM5/14/20
to vim...@googlegroups.com
On 2020-05-14 12:27, boB Stepp wrote:
> On Thu, May 14, 2020 at 08:00:38AM -0500, Tim Chase wrote:
> > :%s/\(\<field \+\w*\)address2.*\n\s*\zs\zepobox\>/\1
>
> Thank you, this works. I had to look up some of the flags you used
> before I understood what you are doing. In my particular example I
> found I did not need to use "\+", "\<" and "\>" to get the desired
> result. But I suspect that you are using these to protect against
> scenarios I am not considering. Would you mind elaborating on why
> you included those additional flags?

The space followed by \+ requires at least one but possibly more than
one space between "field" and the start of the field-name. So it
takes into consideration

field work_address2 STANDARD FIELD -- note multiple spaces

The \< prevents strange situations like

greenfield work_address

by ensuring that "field" stands alone without junk at the beginning.
Unlikely, but handy to specify up front. Likewise the \> prevents
it from finding something like "pobox2". Which you might or might
not want. Adjust according to your requirements.

> As I am trying to understand the "why"s of all of this, it would be
> helpful for my understanding if someone would explain why my regex
> did not work as expected. I am still not seeing it.
> :g/\(field [[:alnum:]_]*\)address2/+s/pobox/\1pobox/

As best I understand, the items captured in the g// portion don't
carry over for reuse in the s// replacement (the s// resets the
capture groups). Otherwsie, I believe it would work since

:g/\(field [[:alnum:]_]*\)address2/+

correctly finds the first lines and

s/pobox/XYZ/

correctly does a substitution, merely lacking access to the captured
\1 group. If you add some brackets around the \1 you'll see the
effect:

:g/\(field [[:alnum:]_]*\)address2/+s/pobox/[\1]pobox/

you'll see resulting lines like

[]pobox

to show that the contents of \1 are empty.

Hope that helps,

-tim




boB Stepp

unread,
May 14, 2020, 2:33:02 PM5/14/20
to vim...@googlegroups.com
On Thu, May 14, 2020 at 12:52:29PM -0500, Tim Chase wrote:
>On 2020-05-14 12:27, boB Stepp wrote:
>> On Thu, May 14, 2020 at 08:00:38AM -0500, Tim Chase wrote:
>> > :%s/\(\<field \+\w*\)address2.*\n\s*\zs\zepobox\>/\1
>>
>> Thank you, this works. I had to look up some of the flags you used
>> before I understood what you are doing. In my particular example I
>> found I did not need to use "\+", "\<" and "\>" to get the desired
>> result. But I suspect that you are using these to protect against
>> scenarios I am not considering. Would you mind elaborating on why
>> you included those additional flags?
>
>The space followed by \+ requires at least one but possibly more than
>one space between "field" and the start of the field-name. So it
>takes into consideration
>
> field work_address2 STANDARD FIELD -- note multiple spaces
>
>The \< prevents strange situations like
>
> greenfield work_address
>
>by ensuring that "field" stands alone without junk at the beginning.
>Unlikely, but handy to specify up front. Likewise the \> prevents
>it from finding something like "pobox2". Which you might or might
>not want. Adjust according to your requirements.

Ah! All of that makes sense now and it is something that I should be
practicing. Thank you!

>> As I am trying to understand the "why"s of all of this, it would be
>> helpful for my understanding if someone would explain why my regex
>> did not work as expected. I am still not seeing it.
>> :g/\(field [[:alnum:]_]*\)address2/+s/pobox/\1pobox/
>
>As best I understand, the items captured in the g// portion don't
>carry over for reuse in the s// replacement (the s// resets the
>capture groups). Otherwsie, I believe it would work since
>
> :g/\(field [[:alnum:]_]*\)address2/+
>
>correctly finds the first lines and
>
> s/pobox/XYZ/
>
>correctly does a substitution, merely lacking access to the captured
>\1 group. If you add some brackets around the \1 you'll see the
>effect:
>
> :g/\(field [[:alnum:]_]*\)address2/+s/pobox/[\1]pobox/
>
>you'll see resulting lines like
>
> []pobox
>
>to show that the contents of \1 are empty.

Hmm. That is what I was speculating -- that "\1" was empty -- and you have
proven that plus giving me another little trouble shooting technique. But
an example in the book "Learning the vi and Vim Editors, 7th ed." by Arnold
Robbins, Elbert Hannah and Linda Lamb made me believe what I was trying was
doable. In chapter 6: Global Replacement on page 82 the authors give this
snippet of text:

mgibox routine;
mgrbox routine;
mgabox routine;

In this exercise we want to replace "box" with "square". The authors give
two ways to accomplish this. One of them is:

:g/mg\([ira]]\)box/s//mg\1square/g

Here where the line found is also the line to change (unlike mine where I
want to change the next line) the "\1" register is accessible. I tried my
case with "g" on the end, but it did not make any difference. Obviously
there is still something here I am not comprehending properly.

Tim Chase

unread,
May 14, 2020, 3:20:19 PM5/14/20
to vim...@googlegroups.com
On 2020-05-14 13:32, boB Stepp wrote:
>> :g/\(field [[:alnum:]_]*\)address2/+s/pobox/[\1]pobox/
>>
>>you'll see resulting lines like
>>
>> []pobox
>>
>>to show that the contents of \1 are empty.
>
> Hmm. That is what I was speculating -- that "\1" was empty -- and
> you have proven that plus giving me another little trouble shooting
> technique. But an example in the book "Learning the vi and Vim
> Editors, 7th ed." by Arnold Robbins, Elbert Hannah and Linda Lamb
> made me believe what I was trying was doable. In chapter 6:
> Global Replacement on page 82 the authors give this snippet of text:
>
> mgibox routine;
> mgrbox routine;
> mgabox routine;
>
> In this exercise we want to replace "box" with "square". The
> authors give two ways to accomplish this. One of them is:
>
> :g/mg\([ira]]\)box/s//mg\1square/g
>
> Here where the line found is also the line to change (unlike mine
> where I want to change the next line) the "\1" register is
> accessible.

In this case it's not whether the line is the same but that the
pattern is the same. By (re)using the pattern from the :g in the :s
by using an empty pattern here:

s//replacement/flags

it (re)captures the portion captured from the same pattern found in
the :g part of the command. So if you happened to have two adjacent
lines both containing "capture", you could do something like

:g/\(capture\)/+s//replacement with \1 in it/

(which has the "+" relative-offset to the line after the initial
match) to replace the one on the 2nd line with "replacement with
capture in it".

Hopefully that sheds a bit of light on matters?

-tim





boB Stepp

unread,
May 14, 2020, 6:03:20 PM5/14/20
to vim...@googlegroups.com
I now see the light! Thanks, Tim!
Reply all
Reply to author
Forward
0 new messages