ProtocolGuy
ProtocolGuy

Reputation: 152

Replace text in file if previous line matches another text

My file looks like this:

FooBarA
foo bar
foo = bar
FooBarB
foo bar
foo = bar
FooBarC
foo bar
foo = bar
...

What I would like to do is to write a script that replaces the bar in foo = bar but only if it belongs to FooBarB. So in the example above only the second bar out of all foo = bar lines should be replaced.

I've played around with sed but I just can't get it done right. I would also like to avoid installing any tools that aren't necessarily pre-installed on the system (I'm on Mac OS), since the script will be used by other team members too.

Upvotes: 1

Views: 657

Answers (4)

potong
potong

Reputation: 58430

This might work for you (GNU sed):

sed '/FooBarB/{:a;n;/^$/b;/foo = bar/!ba;s//foo = baz/}' file

Match on the string FooBarB and start a loop.

Fetch the next line and study it.

If the line is empty the stanza is done, so break out of the loop.

If the line does not contains the string foo = bar, fetch the next line and continue the loop.

Otherwise, substitute the new value for bar and finish the loop.


Alternative (which may work for macos users?):

sed -e '/FooBarB/{:a' -e 'n;/^$/b;/foo = bar/!ba;s//foo = baz/;}' file

Since the OP changed the input data to the question another solution:

sed '/FooBar/h;G;/FooBarB/s/foo = bar/foo = baz/;P;d' file

Upvotes: 2

Ed Morton
Ed Morton

Reputation: 203655

Using any awk in any shell on every Unix box:

$ awk -v tgt='FooBarB' -v val='whatever'  '
    NF==1{tag=$0} (NF>1) && (tag==tgt) && sub(/=.*/,"= "){$0=$0 val}
1' file
FooBarA
foo bar
foo = bar
FooBarB
foo bar
foo = whatever
FooBarC
foo bar
foo = bar

Upvotes: 1

Ionuț G. Stan
Ionuț G. Stan

Reputation: 179119

One way to do it with sed (tested using macOS's sed and GNU sed), would be this:

replace.sed
#!/usr/bin/env sed -Ef

/FooBarB/,/^FooBar/ {
  s/(foo[[:space:]]*=[[:space:]]*).+/\1new-value/
}

Here's what it does:

  1. /FooBarB/,/^FooBar/ matches a range of lines where the first line matches the regex /FooBarB/ and the last line matches the regex /^FooBar/ (which is the start of the next "group"). The comma between the two regexes is the syntax for range matching in sed.

  2. s/(foo[[:space:]]*=[[:space:]]*).+/\1new-value/ — [s]ubstitutes (in the matched range of lines) whatever matches the regex (foo[[:space:]]*=[[:space:]]*).+ with \1new-value, where \1 references the first capturing group in the search regex. The search regex looks for foo followed by optional whitespace, followed by an = sign, followed again by whitespace and then whatever else is there, which in your case is the old value.

You could do it all in just one line, but I wanted to show a version that's a bit more digestible (as far as sed goes, in any case):

sed -E '/FooBarA/,/^FooBar/s/(foo[[:space:]]*=[[:space:]]*).+/\1new-value/' temp.md 

Upvotes: 2

oliv
oliv

Reputation: 13249

For reference, the GNU awk variant:

awk -v v="newvalue" 'BEGIN{FS=OFS="\n";RS=ORS="\n\n"}$1=="FooBarB"{$3="foo = " v}1' file

By using the option -v, the variable v holds the wanted string.

The BEGIN statement sets respectively the input, output field separator, the input and output record separator to one and two carriage return.
That way a record is composed of the block of several lines containing the pattern Foobar[ABC].

The last statement sets the new value by rewriting the third line.

Upvotes: 0

Related Questions