Replace nth line below the searched pattern in a file

I needed to replace 4th line (which contain pattern Y) after a search pattern (say X) in a file. Please note pattern Y is used elsewhere too. So I can't replace it directly. Also note on the 4th line there is only one word Y. Is there any scripting command possible that can do with ease? May be "awk" useful but not sure how to use it exactly.

ex: Pattern to search X in a file. In file we have say

X,
C,
D,
Y,
A,

Now I needed to replace Y which is on 4th line to say B,.


For low numbers of lines:

sed '/X/ { n; n; n; s/Y/B/; }' filename

This is fairly straightforward:

/X/ {      # If the current line matches /X/
  n        # fetch the next line (printing the current one unchanged)
  n        # and the next line
  n        # and the next line
  s/Y/B/   # and in that line, replace Y with B.
}

Since this becomes somewhat unwieldy for large numbers, you may want to consider using awk for such cases:

awk 'NR == marker { sub(/Y/, "B") } /X/ { marker = NR + 3 } 1' filename

That is:

NR == marker { sub(/Y/, "B") }  # if the current line is marked, substitute
/X/ { marker = NR + 3 }         # if the current line matches /X/, mark the one
                                # that comes three lines later for substitution
1                               # print

You could use something like this in awk:

awk '/X/ || f == 1 && sub(/Y,/, "B,") { ++f } 1' file

This sets the flag f to 1 (or true) when the pattern /X/ is matched. When f is true and sub returns a true value (which it does when it performs a substitution), then s is set to true and f back to false. This means that f is only true once, so only one occurrence of Y, will be substituted.

Testing it out on a slightly different example to the one you showed in your question:

$ cat file
Y,
A,
X,
C,
D,
Y,
A,
X,
Y,
$ awk '/X/ && !s { f = 1 } f && sub(/Y,/, "B,") { s = 1; f = 0 } 1' file
Y,
A,
X,
C,
D,
B,
A,
X,
Y,

As you can see, only the Y, after the first X has been replaced. It doesn't matter how many lines there are between the X and the Y,, or if there are more than one X before the first Y,.


If you want to substitute all occurrences of Y, after X appears, then you can simplify the code:

awk '/X/ { f = 1 } f { sub(/Y,/, "B,") } 1' file

As before, this sets the flag f when /X/ has been found. It then performs the substitution every time Y, is found from that point onward.

Testing it out, using the same input as above:

$ awk '/X/ && !s { f = 1 } f { sub(/Y,/, "B,") } 1' file
Y,
A,
X,
C,
D,
B,
A,
X,
B,

another awk alternative, similar to Tom Fenech's, but perhaps easier to read

 awk 'x&&!s{s=sub(/Y/,"B")} /X/{x=1} 1'

if in x and not substituted, substitute; set context to x, when match X; print. Above will replace the first instance. If you want to replace only on line n, you can do the following variation.

awk 'x&&c++==2{sub(/Y/,"B")} /X/{x=1} 1'

this will replace the third line (counter starts at zero) after first X match. Or, if you want to see "3" in the script,

awk 'x&&++c==3{sub(/Y/,"B")} /X/{x=1} 1'

In sed a relative address offset (counted from /REGEX/) you can achieve with:

sed '/X/,+3 {/X/,+2! s/Y/B/}' filename

which means a 4-lines range (from /X/ to X+3) from which in curly braces {} we subtract a sub-range smaller by 1 line (from /X/ to X+2) by adding negation ! after that. Effectively we pass only exact lines we're interested in (X+3) for further processing (substitution in this case).