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
0

6 Answers 6

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: Using ed, the interactive line-editor, to solve the problem:

printf '%s\n' 'g/= Fail$/-2;+3 s/^/#/' 'w' 'q' | ed -s text.in

The editing script

g/= Fail$/-2;+3 s/^/#/
w
q

first applies the substitution s/^/#/ (inserts # at the start of lines) to the lines from two lines above each match of /= Fail$/ and three further lines down from there, in the whole file.

The w writes the changed buffer back to the original file, and q quits the editor.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .