Delete line if line matches "foo", line matches "bar" and line below "baz" matches?

Using sed and / or awk, I would like to be able to delete a line only if it contains the string "foo", and the lines before and after contain the string "bar" and "baz", respectively.

So for this input:

blah
blah
foo
blah
bar
foo
baz
blah

we would remove the second foo, but nothing more, leaving:

blah
blah
foo
blah
bar
baz
blah

I tried using the while loop to read the file line by line, but it is slow, and I cannot figure out how to match the previous and next lines.

Edit - as indicated in the comment, this is the current state of the while loop. Currently only matches the previous line (saved from the previous loop as $ linepre).

linepre=0 
while read line
do 
   if [ $line != foo ] && [ $linepre != bar ]
   then 
       echo $line
   fi
linepre=$line
done < foobarbaz.txt

Pretty ugly.

+4
5

perl . > .

sed .

. , GB. , ( CASE3).

: CASE3 (. ). CASE3 .


1: awk - script, . , , . (CASE3 ):

awk 'BEGIN{p=1;l1=l2=""}
     (NR>2) && p {print l1}
     { p=!(l1~/bar/&&l2~/foo/&&/baz/);
       l1=l2;l2=$0
     }
     END{if (l1!="" && p) print l1
         if (l2!=""     ) print l2}' <file>

, 3 , l1, l2 $0. , l1 . NR=3 . : l1 bar, l2 foo $0 baz, .

2: A sed, , . sed . - , , - . , print , ( x)

 sed '1{x;s/^.*$/print/;x;N};                           #1
      N;                                                #2
      x;/print/{z;x;P;x};x;                             #3
      /bar.*\n.*foo.*\n.*baz/!{x;s/^.*$/print/;x};      #4
      $s/\(bar.*\)\n.*foo.*\n\(.*baz\)/\1\n\2/;         #5
      D' <file>                                         #6
  • line #1 , print (x;s...;x) (N)
  • #2
  • line #3 , , P \n , z
  • #4 , . , , print
  • #5,
  • #6 \n #1 .

.

comment: , , : s/^/P:/;l;s/^P://;x;s/^/H:/;l;s/^H://;x. P:, H: .

:

# bar-foo-baz test file
# An asterisk indicates the foo
# lines that should be removed
<CASE0 :: default case>
bar
foo (*)
baz
<CASE1 :: reset cycle on second line>
bar
foobar
foo (*)
baz
<CASE2 :: start cycle at end of previous cycle>
bar
foo (*)
bazbar
foo (*)
baz
<CASE3 :: nested cases>
bar
foobar (*)
foobaz (*)
baz
<CASE4 :: end-of-file case>
bar
foo

: (, , )

awk: CASE3

awk '!/baz/&&(c==2){print foo}
     /bar/         {c=1;print;next}
     /foo/ &&(c==1){c++;foo=$0;next}
                   {c=0;print}
     END{if(c==2){print foo}}' <file>

, , foo, , bar. , foo .

  • !/baz/&&(c==2){print foo}: . bar-foo baz, foo.

  • /bar/{c=1;print;next}: . bar, c 1, . bar . CASE1 CASE2.

  • /foo/&&(c==1){c++;foo=$0;next}: bar-foo. foo .

  • {c=0;print}, , , bar bar-foo. reset .

  • END{if(c==2){print foo}} CASE4

gawk: CASE3

awk 'BEGIN{ORS="";RS="bar[^\n]*\n[^\n]*foo[^\n]*\n[^\n]*baz"}
     {sub(/\n[^\n]*foo[^\n]*\n/,"\n",RT); print $0 RT}' <file>

RS bar[^\n]*\n[^\n]*foo[^\n]*\n[^\n]*baz, . [^\n]*\n[^\n]* , \n, RS bar-foo-baz. RT sub, foo .

RT ( gawk) , , RS, . , .

sed: CASE1, CASE2, CASE3, CASE4

sed -n '/bar/{N;/\n.*foo/{N;/foo.*\n.*baz[^\n]*$/{s/\n.*foo.*\n/\n/}}};p' <file>
  • /bar/{N;...} bar, (N)
  • /\n.*foo/{N;...}, foo , (N)
  • /foo.*\n.*baz[^\n]*$/{s/\n.*foo.*\n/\n/}, foo, , , baz, , foo. barfoo\nfoobaz\ncar
+5

:

$ cat ip.txt 
blah
bar
blah
foo
blah
bar
foo
baz
blah
bar
foobar
foo
baz
asdf

perl ,

$ perl -0777 -pe 's/bar.*\n\K.*foo.*\n(?=.*baz)//g' ip.txt
blah
bar
blah
foo
blah
bar
baz
blah
bar
foobar
baz
asdf
  • -0777,
  • bar.*\n\K , bar
  • .*foo.*\n foo
  • (?=.*baz) baz
  • . . ? . , 3 .
+3

(GNU sed):

sed ':a;/bar/!b;n;/foo/!ba;N;s/^.*\n\(.*baz\)/\1/;t;P;D' file

bar, . , bar, . foo, , bar. ( foo) , baz. , foo, , baz, . baz, , foo, , , bar.

, :

sed -zr 's/(bar[^\n]*)\n[^\n]*foo[^\n]*(\n[^\n]*baz)/\1\2/g' file
+3

1st: ( ) - , .

awk '/^bar/ && getline var ~ /^foo/ && getline var1 ~ /^baz/{print "bar" ORS "baz";next} 1'  Input_file

2nd: awk .

awk '/bar/{val=FNR} /^foo/ && ++val==FNR{value=$0;getline;if($0 ~ /^baz/){print value ORS $0;val="";next} else {print value}} 1'    Input_file

:

1st: bar string foo baz , .

2nd: bar, baz foo, .

+2

- sed

sed -r ':l; N; $!bl; s/(^|\nbar\n)foo\n(baz$|\n)/\1\2/g' input.txt

, , , , , -z:

sed -zr 's/(^|\nbar\n)foo\n(baz\n|$)/\1\2/g' input.txt

-z = single lines using NUL characters. This option can be used to store all text in memory (if the text does not have NUL characters).

The second option is to use grep and sed

grep --color=always -Pz '\^|\nbar\n\Kfoo\n(?=baz\n)' input.txt | sed '/31m/d'

Both options put all the text in memory before processing, so they are not optimal for large files.

Enter

blah
blah
foo
blah
bar
foo
baz
blah

Output

blah
blah
foo
blah
bar
baz
blah
0
source

Source: https://habr.com/ru/post/1694481/


All Articles