5

I have managed to do this with a super long find-replace but I'm sure there's a simpler way:

I have a file of the form:

E01 2017 10 24 00 00 00-1.90001e-04-8.79280e-12 0.00000e+00
            3.20000e+01 8.53750e+01 2.90408e-09 3.04855e+00
            4.08463e-06 3.01205e-04 1.49385e-06 5.44061e+03
            1.72800e+05 6.14672e-08-1.45096e+00-7.45058e-09
            9.95466e-01 3.22187e+02-2.16382e+00-5.72952e-09
            7.50018e-11 2.58000e+02 1.90000e+03 0.00000e+00
            3.12000e+00 0.00000e+00-4.49424e-09 0.00000e+00
            1.73500e+05 0.00000e+00 0.00000e+00 0.00000e+00
E01 2017 10 24 00 00 00-1.90001e-04-8.79280e-12 0.00000e+00
            3.20000e+01 8.53750e+01 2.90410e-09 3.04855e+00
            4.08460e-06 3.01205e-04 1.49380e-06 5.44061e+03
            1.72800e+05 6.14672e-08-1.45100e+00-7.45058e-09
            9.95470e-01 3.22187e+02-2.16380e+00-5.72952e-09
            7.50020e-11 5.21000e+02 1.90000e+03 0.00000e+00
            0.00000e+00 0.00000e+00-4.49420e-09-5.35510e-09
            1.73400e+05 0.00000e+00 0.00000e+00 0.00000e+00
E02 2017 10 24 00 00 00-5.65248e-06 9.24880e-13 0.00000e+00
            3.20000e+01 1.05375e+02 2.89100e-09-7.19798e-01
            4.93710e-06 2.56068e-04 2.77200e-06 5.44061e+03
            1.72800e+05 4.84287e-08-1.49240e+00-8.75443e-08
            9.95490e-01 2.94906e+02-1.54180e+00-5.75452e-09
            1.30070e-10 2.58000e+02 1.90000e+03 0.00000e+00
            0.00000e+00 0.00000e+00-6.99620e-09 0.00000e+00
            1.73500e+05 0.00000e+00 0.00000e+00 0.00000e+00
...

Each line starting with E represents the beginning of a "block". All blocks are 8 lines long, and the width of the columns is fixed for the whole file. I want to delete a block if the second element of the sixth line of the block is (not) 2.58000e+02.

So after deleting all blocks which do have 2.58000e+02 as the second element of their sixth line I would expect:

E01 2017 10 24 00 00 00-1.90001e-04-8.79280e-12 0.00000e+00
            3.20000e+01 8.53750e+01 2.90410e-09 3.04855e+00
            4.08460e-06 3.01205e-04 1.49380e-06 5.44061e+03
            1.72800e+05 6.14672e-08-1.45100e+00-7.45058e-09
            9.95470e-01 3.22187e+02-2.16380e+00-5.72952e-09
            7.50020e-11 5.21000e+02 1.90000e+03 0.00000e+00
            0.00000e+00 0.00000e+00-4.49420e-09-5.35510e-09
            1.73400e+05 0.00000e+00 0.00000e+00 0.00000e+00

After deleting all blocks whose second element in the sixth line is not 2.58000e+02 I would expect:

E01 2017 10 24 00 00 00-1.90001e-04-8.79280e-12 0.00000e+00
            3.20000e+01 8.53750e+01 2.90408e-09 3.04855e+00
            4.08463e-06 3.01205e-04 1.49385e-06 5.44061e+03
            1.72800e+05 6.14672e-08-1.45096e+00-7.45058e-09
            9.95466e-01 3.22187e+02-2.16382e+00-5.72952e-09
            7.50018e-11 2.58000e+02 1.90000e+03 0.00000e+00
            3.12000e+00 0.00000e+00-4.49424e-09 0.00000e+00
            1.73500e+05 0.00000e+00 0.00000e+00 0.00000e+00
E02 2017 10 24 00 00 00-5.65248e-06 9.24880e-13 0.00000e+00
            3.20000e+01 1.05375e+02 2.89100e-09-7.19798e-01
            4.93710e-06 2.56068e-04 2.77200e-06 5.44061e+03
            1.72800e+05 4.84287e-08-1.49240e+00-8.75443e-08
            9.95490e-01 2.94906e+02-1.54180e+00-5.75452e-09
            1.30070e-10 2.58000e+02 1.90000e+03 0.00000e+00
            0.00000e+00 0.00000e+00-6.99620e-09 0.00000e+00
            1.73500e+05 0.00000e+00 0.00000e+00 0.00000e+00

My attempt

To delete the blocks which contain 2.58000e+02

First delete the block themselves with:

:%s/^E[0-9 .e+-]\+\n\([0-9 .e+-]\+\n\)\{4}[ ]\+[0-9.e+-]\+ 2.58000e+02\([0-9 .e+-]\+\n\)\+//g

Then remove the blank lines which remain after the blocks that have been preserved:

:%s/\n^[ ]\{}$//g

To delete the blocks which do not contain 2.58000e+02:

This one was tougher. What I did is store the result of the previous command in a separate file, and from terminal perform the diff between the original file and the new file, and keep the lines which are in the original one and not the second one. This however requires leaving ViM.

Is there a simpler way to achieve these two things?

3 Answers 3

8

To delete the blocks containing 2.58000e+02, you can use following global command

:g/\v^E(.*$\n){5}\s+.{-}2.58000e\+02/,+7d_

This breaks down as

:g/                                 starts a global command
\v^E(.*$\n){5}\s+.{-}2.58000e\+02   searches for blocks containing 2.58
/,+7d_                              sets a range from the start of a match up until 
                                    the next 7 lines and deletes the lines

To retain the blocks containing 2.58000e+02, instead of searching and deleting blocks not containing your search pattern I'd suggest to use a register to copy the required blocks to. This way you can reuse your search pattern.

:let @a=''|g/\v^E(.*$\n){5}\s+.{-}2.58000e\+02/,+7y A

This breaks down as

:let @a=''|g/                       clears the a register and starts a global command
\v^E(.*$\n){5}\s+.{-}2.58000e\+02   searches for blocks containing 2.58
/,+7y A                             sets a range from the start of a match up until 
                                    the next 7 lines and appends the lines to a register

Following all this, your workflow could be as follows

  1. Search your pattern: /\v^E(.*$\n){5}\s+.{-}2.58000e\+02
  2. Copy all matching blocks to register a: :let @a=''|g//,+7y A
  3. Delete all matching blocks from buffer: :g//,+7d_
  4. Paste register a into a new buffer: "ap

Edit

even easier

  1. Search your pattern: /\v^E(.*$\n){5}\s+.{-}2.58000e\+02
  2. Cut all matching blocks to register a: :let @a=''|g//,+7d A
  3. Paste register a into a new buffer: "ap
1
  • 1
    Your edit was a super simple way of solving the issue, thanks! Dec 15, 2017 at 9:38
1

To show that this can be done in Awk (gawk specifically).

gawk -v FPAT='-?.{7}e[-+]..' -v RS='\nE' '{ gsub(/^[0-9]/, "E&"); gsub(/\n$/, "") } $21 == "2.58000e+02"' input.txt > output.txt

For readability:

{
    gsub(/^[0-9]/, "E&");
    gsub(/\n$/, "")
}

$21 == "2.58000e+02"

Some Gawk details:

  • Use FPAT to define what is considered a field. In this case something that looks like a number in scientific noation
  • Set the records separator, RS, to a new line followed by E
  • Use substitutions (gsub) to add back the E to the block and remove trailing newline
  • Output only blocks which field 21 matches 2.58000e+02

You can run this in Vim via the filter command, :%!. See :h :range!

0

:%s;\v^E\d* \d*( \d\d)+.\n(\s+.\n){4}\s+([- ]\d+.\d+e[-+]\d+ )(2.580+e+\d+)@!.\n(\s+.\n)*;;

worked for me, at least using your sample from the question.

"Simply" make sure you match up to where the 2.58... should appear, and use a zero-width assertion.

If you have to do this more than once, for the love of G-d, write a Perl script (probably only be a couple of lines :o)) and run it with

:%!perl my-filter.pl

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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