3

I have a big file (more than 2000 lines). Where I have to insert a # at the beginning of 2 above lines and at the beginning of 1 below line after finding a pattern. Also insert # at the beginning of line where pattern found. Environment is Red Hat Linux. Also if you can explain, that would be great.

Take an example, please see below texts, Search "Fail" and # 2 lines before that and 1 line after that string (beginning of line). Also # line containing string "Fail".

Name
Number
Reason = Pass
Reasult
Name
Number
Reason = Pass
Reasult
Name
Number
Reason = Fail
Reasult
Name
Number
Reason = Pass
Reasult
Name
Number
Reason = Fail
Reasult
Name
Number
Reason = Pass
Reasult
3

I would suggest to use perl:

perl -p0e 's/(.*\n)(.*\n)(.*Fail\n)/#\1#\2#\3#/g' file

Here is how it works:

  • -p: print program in the loop over all input lines
  • -0: assume null as record separator
  • -e: execute program from the command line
  • s/x/y/g: substitute y for x anywhere in the file
  • (): group together regular expressions
  • .*: any character except newline repeated zero or more times
  • \n: newline
  • \1,\2,\3: access pattern from nth group ()

Output:

Name
Number
Reason = Pass
Reasult
Name
Number
Reason = Pass
Reasult
#Name
#Number
#Reason = Fail
#Reasult
Name
Number
Reason = Pass
Reasult
#Name
#Number
#Reason = Fail
#Reasult
Name
Number
Reason = Pass
Reasult
1

Here's a sed solution using a sliding window (so there are never more than four lines in the pattern space at a time):

sed '1{N;N;};$!N;/.*\n.*\n.*Fail.*\n.*/{s/^/#/;s/\n/&#/g;};P;D' infile

On first line it reads in the Next two lines (so now there are three lines in the pattern space).
Then, for each input line (including the first one) it pulls in the Next line (so now there are four lines in the pattern space). If the third line in the pattern space matches Fail, it prepends each line in the pattern space with a #. Then, regardless, it Prints up to the first \newline and then Deletes up to the first \newline, restarting the cycle.

0
$ sed -r 'H;1h;$!d;x; s/\n([^\n]*)\n([^\n]*)\n([^\n]*Fail[^\n]*)\n/\n#\1\n#\2\n#\3\n#/g' file
Name
Number
Reason = Pass
Reasult
Name
Number
Reason = Pass
Reasult
#Name
#Number
#Reason = Fail
#Reasult
Name
Number
Reason = Pass
Reasult
#Name
#Number
#Reason = Fail
#Reasult
Name
Number
Reason = Pass
Reasult

How it works

  • H;1h;$!d;x

    These commands read the whole file in.

  • s/\n([^\n]*)\n([^\n]*)\n([^\n]*Fail[^\n]*)\n/\n#\1\n#\2\n#\3\n#/g

    This looks for four consecutive lines with Fail in the third line. If that is found, then # are placed after each newline character.

    In more detail, a substitute command looks like s/old/new where old is a regular expression. In our case, it is \n([^\n]*)\n([^\n]*)\n([^\n]*Fail[^\n]*)\n. Let's break that into its four parts:

    1. \n([^\n]*) finds the first line and saves it in group 1.

    2. \n([^\n]*) finds the second line and saves it in group 2.

    3. \n([^\n]*Fail[^\n]*) finds the third line but only matches if this line contains the word Fail.

    4. \n matches the fourth newline. (The text of the fourth line is not saved: we don't need it.)

    If four lines match the above, then we replace them with \n#\1\n#\2\n#\3\n# which is the same as the input except that # are added after each newline character, \n.

    Mac OSX (BSD)

The above was tested on GNU sed. If using BSD sed, try:

sed -E 'H;1h;$!d;x; s/\n([^\n]*)\n([^\n]*)\n([^\n]*Fail[^\n]*)\n/\n#\1\n#\2\n#\3\n#/g' file
0

Using a 4-line "window" in Perl:

perl -ne '
    push @w, $_;
    if (4 == @w) {
        if ($w[2] =~ /Fail/) {
            s/^/#/ for @w;
        }
        print @w;
        @w = ();
    }
' < input-file > output-file
  • -n reads the input line by line.
  • @w is the window, it accumulates lines until there are 4 of them. At that moment, the third one is matched against /Fail/, and if present, each line in the window is prefixed with a #. Then, the window is printed and emptied.

Note: Might not print the last lines of the input if the last block is shorter than 4 lines.

0

More simplyfied variant of @don_crissti 's script

sed ':a;/\(.*\n\)\{2\}/{P;D};N;/= Fail$/! ba;N;s/^/# /gm'
0

Just for fun: This is a shell loop over the ed interactive line-editor that solves the problem:

while ed text.in <script.ed >/dev/null; do
  :  # nothing here
done

The file script.ed contains

/^[^#].*Fail/-2
.,+3s/^/#/
w
  1. The first line of the editing script finds the next line containing the word Fail that has not already been prepended with a #, and steps back two lines from that.

  2. The second line of the script replaces the start of the line with # (i.e. prepends # to the line), for the current line and three more lines forward.

  3. The third line of the script writes the file back to disk.

The loop in the shell will exit as soon as ed exits with a non-zero exit status. This will happen when it can't find another line matching the regular expression on line one of the editing script.

Alternatively, without a separate editing script in a file:

while ed text.in >dev/null <<ED_END
/^[^#].*Fail/-2
.,+3s/^/#/
w
ED_END
do
  :  # nothing here
done

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.