How to replace string in specific paragraph only?

41 views
Skip to first unread message

Igor

unread,
Sep 19, 2020, 6:51:34 AM9/19/20
to vim_use
Hi,
I have the following sample text file:

XXX ********  ****** ******
    ******* ***** ******
    *** aaa **** ****
    *** *** ******** **

YYY ********  ****** ******
    **** aaa *** *** ****
    ******* ***** ******
    *** *** ******** **

XXX ********  ****** ******
    * aaa *** **** ****
    ******* ***** ******
    *** *** ******** **

XXX ********  ****** ******
    *** *** ******** **
    * ** **** ***** ******
    aaa **** **** ***

ZZZ ********  ****** ******
    ****** **** **** aaa
    ******* ***** ******
    *** *** ******** **


with a lot more paragraphs.

REPLACE RULE:
I need to replace string "aaa" with "bbb" only in paragraphs that starts with "XXX".

ADDITIONAL INFO:
1. String "aaa" can be anywhere in the file (in paragraphs that do not start with "XXX").
2. Paragraphs are blocks of text separated with two new -ines characters.
3. Text is Unix like text (only new line character at the end of line to break a line).
4. There are no spaces or tabs at the end of line.
5. Paragraphs are always separated by two new-line characters, never more (like three or more).
6. There is no special order of "XXX" paragraphs inside file. "XXX" paragraphs appear randomly inside file.
7. The "XXX" string is at the beginning of line (no other character before it).

Regards

Igor

unread,
Sep 19, 2020, 7:43:43 AM9/19/20
to vim_use
Hi,
I have come out with one of the possible solution:

" move to first line/character in buffer
gg

" find "XXX", visually select paragraph and replace "aaa" with bb", then move to the next paragraph.
execute "normal! /XXX\<cr>v}:s/aaa/bbb/g\<cr>\<esc>}"

Above command works perfectly for first paragraph. Now I need to execute it many times to the end of buffer. I can manually execute above command many times and it works perfectly.
But, how to execute above "execute" command automatically so many times that it gets to the end of buffer. Why? Because finally, I would like to save the vim commands to file and execute vim commands from file to affect whole buffer (e.g. with :source command).

Regards

Sven Guckes

unread,
Sep 19, 2020, 8:08:25 AM9/19/20
to vim_use
* Igor <igo...@gmail.com> [2020-09-19 12:55]:
> I have the following sample text file: ...
> *REPLACE RULE:*
> I need to replace string "aaa" with "bbb"
> only in paragraphs that starts with "XXX".

:g/^XXX/?^$?,/^$/s:aaa:bbb:g

i wish i could write that with less characters. ;)

Sven

--
#Kielux2020 https://kielux.de Sep18+19 11-19h, 10-18h
18. Kieler Open Source und Linux Tage
2020-09-08: 16 Vorträge + 8 Workshops
http://www.guckes.net/kielux/schedule.html

Salman Halim

unread,
Sep 19, 2020, 9:16:05 AM9/19/20
to vim_use

On Sat, Sep 19, 2020, 08:08 Sven Guckes <guc...@guckes.net> wrote:
* Igor <igo...@gmail.com> [2020-09-19 12:55]:
> I have the following sample text file: ...
> *REPLACE RULE:*
> I need to replace string "aaa" with "bbb"
> only in paragraphs that starts with "XXX".

  :g/^XXX/?^$?,/^$/s:aaa:bbb:g

i wish i could write that with less characters. ;)

Sven

I can't think of a more efficient way. The only suggestion is to perhaps replace the last 'g' with a 'ge' on the off chance that a paragraph doesn't contain the text so that the whole thing won't give an error message. 

Salman

c.wil...@btinternet.com

unread,
Sep 19, 2020, 10:41:18 AM9/19/20
to vim...@googlegroups.com
--
Hi

didn't work for me - said bad range or words to that effect. This does:

:g/^XXX/.,/^$/s:aaa:bbb:g

I don't imagine aaa can occur in some other word, but in case, perhaps \<aaa\>.

Chris W
--
You received this message from the "vim_use" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

---
You received this message because you are subscribed to the Google Groups "vim_use" group.
To unsubscribe from this group and stop receiving emails from it, send an email to unsub...@googlegroups.com">vim_use+unsub...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/vim_use/CANuxnEdwssZCjK_hpJKJ_25oGnMnq1HRsToug1%3DoO8P1ODs-eg%40mail.gmail.com.

Salman Halim

unread,
Sep 19, 2020, 10:49:10 AM9/19/20
to Vim Users


On Sat, Sep 19, 2020, 10:41 'c.wil...@btinternet.com' via vim_use <vim...@googlegroups.com> wrote:




------ Original Message ------
From: "Salman Halim" <salma...@gmail.com>
To: "vim_use" <vim...@googlegroups.com>
Sent: Saturday, 19 Sep, 2020 At 14:15
Subject: Re: How to replace string in specific paragraph only?


On Sat, Sep 19, 2020, 08:08 Sven Guckes <guc...@guckes.net> wrote:
* Igor <igo...@gmail.com> [2020-09-19 12:55]:
> I have the following sample text file: ...
> *REPLACE RULE:*
> I need to replace string "aaa" with "bbb"
> only in paragraphs that starts with "XXX".

:g/^XXX/?^$?,/^$/s:aaa:bbb:g

i wish i could write that with less characters. ;)

Sven

I can't think of a more efficient way. The only suggestion is to perhaps replace the last 'g' with a 'ge' on the off chance that a paragraph doesn't contain the text so that the whole thing won't give an error message.

Salman

--
Hi

didn't work for me - said bad range or words to that effect. This does:

:g/^XXX/.,/^$/s:aaa:bbb:g

I don't imagine aaa can occur in some other word, but in case, perhaps \<aaa\>.

Chris W

You're going from the line that contains the text to the end of the paragraph. Sven's solution didn't assume that the line was at the beginning of the paragraph and started by first going back up to a blank line, just in case.

What happened when you tried it?

I didn't type it in, because I'm using my mobile phone right now, but it looked okay. Again, other than the addition of the e at the end. 

Salman

c.wil...@btinternet.com

unread,
Sep 19, 2020, 10:53:52 AM9/19/20
to vim...@googlegroups.com



Hi Salman


:g/^XXX/?^$?,/^$/s:aaa:bbb:g
E16: Invalid range
Press ENTER or type command to continue


It seems to me that the start line of the range we need is the current line with the XXX.


regards - Chris

--
--
You received this message from the "vim_use" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

---
You received this message because you are subscribed to the Google Groups "vim_use" group.
To unsubscribe from this group and stop receiving emails from it, send an email to unsub...@googlegroups.com">vim_use+unsub...@googlegroups.com.

Tim Chase

unread,
Sep 19, 2020, 11:27:47 AM9/19/20
to vim...@googlegroups.com
On 2020-09-19 14:08, Sven Guckes wrote:
>> I need to replace string "aaa" with "bbb"
>> only in paragraphs that starts with "XXX".
>
> :g/^XXX/?^$?,/^$/s:aaa:bbb:g

The only gotcha with this is that a paragraph must have a blank line
before & after it. I've gotten stung doing this exact format of
command because the match was in the first/last paragraph of the
document. So you need to also ("\|") accept a match of the beginning
of the file ("\%^") and end of the file ("\%$")

:g/aaa/?^$\|\%^?,/^$\|\%$/s:aaa:bbb:g

As Sven says, it's ugly. But it's doable and fairly concise in what
you're describing:

Find all the "aaa"
Starting there, search backwards to the previous blank line or BOF
From there, search forwards to the next blank line or EOF
Substitute "aaa" → "bbb" on all the lines of that range

Alternatively, you could hack it with normal mode, something like

:g/aaa/norm vip:s//bbb^V^M

where ^V^M is a control-V followed by a control-M (which makes this
nasty to put in a mapping or vimrc). This works because vim knows
that a "paragraph" is bounded by empty lines *or* BOF/EOF and does
the ugly work for us.

I can't imagine doing this in pretty much any other text editor
without typing a LOT more.

-tim



Igor

unread,
Sep 19, 2020, 3:02:46 PM9/19/20
to vim_use
Hi,
thank you all for kind suggestions. I would like to use the simplest solution,
that is easily typed from the head and well understood. I specially like bellow
solution, that I have tested and it works fine.

:g/^XXX/.,/^$/s:aaa:bbb:g

Just one question,  why is there a "." character in command?
If I understand it correctly, a "." character means the line where cursor is,
and with the range used in global command, "current line" (the dot) is the
first line in range and that is the line with "XXX". If we simplify, we can
omitte the "." character and we get:

:g/^XXX/,/^$/s:aaa:bbb:g

The above command means:
- search for the line where at the beginning of the line is XXX string, and this becomes beginning of the range,
- then search empty line "^$" that is actually end of paragraph, and this becomes ending of range,
- then execute search and replace command within (and including) beginning and ending of range.

Thanks for help

Tim Chase

unread,
Sep 19, 2020, 3:28:49 PM9/19/20
to Igor, vim...@googlegroups.com
On 2020-09-19 12:02, Igor wrote:
> I specially like bellow solution, that I have tested
> and it works fine.
>
> :g/^XXX/.,/^$/s:aaa:bbb:g
>
> Just one question, why is there a "." character in command?
> If I understand it correctly, a "." character means the line where
> cursor is,
> and with the range used in global command, "current line" (the dot)
> is the first line in range and that is the line with "XXX". If we
> simplify, we can omitte the "." character and we get:
>
> :g/^XXX/,/^$/s:aaa:bbb:g

Correct.

> The above command means:
> - search for the line where at the beginning of the line is XXX
> string, and this becomes beginning of the range,
> - then search empty line "^$" that is actually end of paragraph,
> and this becomes ending of range,
> - then execute search and replace command within (and including)
> beginning and ending of range.

Exactly! Once you start seeing this pattern, it's hard to unsee and
you find yourself using this regularly :-D

-tim


Grant Taylor

unread,
Sep 19, 2020, 4:06:31 PM9/19/20
to vim...@googlegroups.com
On 9/19/20 9:27 AM, Tim Chase wrote:
> The only gotcha with this is that a paragraph must have a blank
> line before & after it. I've gotten stung doing this exact format
> of command because the match was in the first/last paragraph of the
> document. So you need to also ("\|") accept a match of the beginning
> of the file ("\%^") and end of the file ("\%$")
>
> :g/aaa/?^$\|\%^?,/^$\|\%$/s:aaa:bbb:g
>
> As Sven says, it's ugly. But it's doable and fairly concise in what
> you're describing:
>
> Find all the "aaa"
> Starting there, search backwards to the previous blank line or BOF
> From there, search forwards to the next blank line or EOF
> Substitute "aaa" → "bbb" on all the lines of that range

Very interesting.

Thank you for that explanation Tim.

> Alternatively, you could hack it with normal mode, something like
>
> :g/aaa/norm vip:s//bbb^V^M
>
> where ^V^M is a control-V followed by a control-M (which makes this
> nasty to put in a mapping or vimrc). This works because vim knows
> that a "paragraph" is bounded by empty lines *or* BOF/EOF and does
> the ugly work for us.

ACK

Would it be worth while to include word boundaries \< and \> so that you
don't accidentally get a sub-string?

:g/aaa/?^$\|\%^?,/^$\|\%$/s:\<aaa\>:bbb:g

Also, is there any way to group the first "aaa" so that it could be
(back) referenced in the substitution? }:-)

> I can't imagine doing this in pretty much any other text editor
> without typing a LOT more.

~chuckle~



--
Grant. . . .
unix || die

Tim Chase

unread,
Sep 19, 2020, 5:13:19 PM9/19/20
to vim...@googlegroups.com
On 2020-09-19 14:06, 'Grant Taylor' via vim_use wrote:
> > :g/aaa/norm vip:s//bbb^V^M
> >
> > where ^V^M is a control-V followed by a control-M (which makes
> > this nasty to put in a mapping or vimrc). This works because vim
> > knows that a "paragraph" is bounded by empty lines *or* BOF/EOF
> > and does the ugly work for us.
>
> ACK
>
> Would it be worth while to include word boundaries \< and \> so
> that you don't accidentally get a sub-string?

My assumption was that "aaa" was a meta-regex that matched whatever
was needed, including any word-boundaries or other matching/context
the OP needed.

> Also, is there any way to group the first "aaa" so that it could be
> (back) referenced in the substitution? }:-)

Depends on what you mean. There are multiple regex in play:

- the thing looked for in the g/first/

- the boundary looked for when looking backwards (empty line or BOF)

- the boundary looked for when looking forwards (empty line or EOF)

- the thing looked for in the s// command

Using the :norm method, you don't have the two boundary regexen to
change the most-recently-used-search, so the regex in the :g can be
preserved into the :s// command so you don't have to duplicate it.

Either way, whatever regex the :s// searches for can capture all (for
replacement with "&") or subsets (with "\(…\)" for replacements with
"\1" through "\9"), and then use those captured bits in the
replacement. Including if you use the :norm method and capture them
in the :g portion. Here I look for "G" followed by any number of "C"s
followed by another "G" and then replace them with a "T" followed by
the "C"s we captured, followed by another "T":

:g/G\(C*\)G/norm vip:s//T\1T/g^V^M

-tim






Grant Taylor

unread,
Sep 19, 2020, 6:43:33 PM9/19/20
to vim...@googlegroups.com
On 9/19/20 3:13 PM, Tim Chase wrote:
> My assumption was that "aaa" was a meta-regex that matched whatever
> was needed, including any word-boundaries or other matching/context
> the OP needed.

That seems like a reasonable assumption. Thank you for clarifying.

> Depends on what you mean. There are multiple regex in play:
>
> - the thing looked for in the g/first/

This first one is what I had in mind when I asked the question.

> - the boundary looked for when looking backwards (empty line or BOF)
>
> - the boundary looked for when looking forwards (empty line or EOF)
>
> - the thing looked for in the s// command

This is actually where I was wanting to use the contents of g/first/. ;-)

> Using the :norm method, you don't have the two boundary regexen to
> change the most-recently-used-search, so the regex in the :g can be
> preserved into the :s// command so you don't have to duplicate it.

If I'm understanding you correctly, the following regex could be used:

:g/aaa/?^$\|\%^?,/^$\|\%$/s::bbb:g

> Either way, whatever regex the :s// searches for can capture all (for
> replacement with "&") or subsets (with "\(…\)" for replacements
> with "\1" through "\9"), and then use those captured bits in the
> replacement.

I keep forgetting that & matches the full capture.

> Including if you use the :norm method and capture them in the :g
> portion. Here I look for "G" followed by any number of "C"s followed
> by another "G" and then replace them with a "T" followed by the
> "C"s we captured, followed by another "T":
>
> :g/G\(C*\)G/norm vip:s//T\1T/g^V^M

This seems like the typical subset mentioned above used in conjunction
with the "last search pattern" (nomenclature?).

Thank you Tim. As always you make it easy to learn new things. :-)

Tim Chase

unread,
Sep 19, 2020, 7:09:06 PM9/19/20
to vim...@googlegroups.com
On 2020-09-19 16:43, 'Grant Taylor' via vim_use wrote:
> > - the thing looked for in the s// command
>
> This is actually where I was wanting to use the contents of
> g/first/. ;-)
>
>> Using the :norm method, you don't have the two boundary regexen
>> to change the most-recently-used-search, so the regex in the :g
>> can be preserved into the :s// command so you don't have to
>> duplicate it.
>
> If I'm understanding you correctly, the following regex could be
> used:
>
> :g/aaa/?^$\|\%^?,/^$\|\%$/s::bbb:g

The problem is that here you have four different searches. Empty
patterns (in the :s// command) reuse the most recent one:

:g/aaa/ ?^$\|\%^? , /^$\|\%$/ s::bbb:g
1 2 3 4

The most-recent-search gets set to "aaa" (1), then set to "^$\|\%^"
(2), then set to "^$\|\%$" (3), leaving (3) as the most-recent for
reuse in the :s command at (4) which isn't what you want. You could
simplify in the opposite direction, making your pattern *any*
paragraph-boundary and reusing it instead:

:g/aaa/ ?^$\|\%^\|\%$? , // s:aaa:bbb:g
1 2 3

(reusing #2 at #3)


Which is also why it *does* work for the :norm version because that
one doesn't tromp on the most-recent-search like the
range-relative-to-each-match version does. Similarly, if your
relative range didn't involve searching such as

:g/aaaa/-3,+2s::bbbb:g

it would work.

> > :g/G\(C*\)G/norm vip:s//T\1T/g^V^M
>
> This seems like the typical subset mentioned above used in
> conjunction with the "last search pattern" (nomenclature?).

For more on this (though its prose could use a little reworking for
additional clarity)

:help last-pattern

> Thank you Tim. As always you make it easy to learn new things. :-)

I find it fun, so there's that. :-)

-tim




Reply all
Reply to author
Forward
0 new messages