8

I have Hugh file with following details :

define host{ use generic-host ; Name of host template to use host_name ServerA_172.29.16.102 alias ServerA_172.29.16.102 address 172.29.16.102 check_command check-host-alive max_check_attempts 3 notification_interval 120 notification_period 24x7 } define host{ use generic-host ; Name of host template to use host_name ServerB_172.29.16.103 alias ServerB_172.29.16.103 address 172.29.16.103 check_command check-host-alive max_check_attempts 3 notification_interval 120 notification_period 24x7 } 

What I want, Search "address 172.29.16.102" and Delete 4 line above and after 5 line .

I have tried following with sed but not work

sed '$N;$N;N;/address 172.29.16.102/,+5d' hosts 
3
  • MUST it be sed, or are you open to other tools? Commented Sep 6, 2013 at 18:22
  • if you are deleting both the 4 lines before AND 5 after, surely sed -n '/\s*address/p;' file will solve it? Which is the same as grep 'address' file? Or am I missing something here? What is the output meant to look like? Commented Sep 6, 2013 at 20:28
  • there any many entry in input file, I just want to remove some container means from define { to } if that address match, It should not remove any other content, that's it Commented Sep 6, 2013 at 20:30

6 Answers 6

8

If each define_host section is separated by one or more newlines, this is exactly the kind of problem GNU awk's multiple line record support is meant to solve

awk -v RS= '!/172.29.16.102/{printf $0""RT}' 
4
  • I will accept this ans, could you please explain it. thanks.. Commented Sep 10, 2013 at 5:30
  • This also not work, very strange things happen, this also remove other entry which is not related .. Commented Sep 10, 2013 at 13:28
  • @RahulPatil, interesting. Did that other entry happen to contain 172.29.16.102? Also, was it clearly separated from other entries by blank lines? Commented Sep 10, 2013 at 14:04
  • nop, there not related to that Commented Sep 10, 2013 at 14:24
5

When ever I see this type of question my gut tells me this is a job for grep. However grep's ability to inverse (-v) the results when using the before & after switches (-B .. & -A ..) doesn't allow for this.

However this clever approach of calling grep 2 times does it much cleaner than any of the awk or sed solutions that I've seen to date.

$ grep -v "$(grep -B 4 -A 5 'address 172.29.16.102' <file>)" <file> 

Example

Here's some sample data.

$ cat sample.txt define host{ use generic-host ; Name of host template to use host_name ServerA_172.29.16.102 alias ServerA_172.29.16.102 address 172.29.16.102 check_command check-host-alive max_check_attempts 3 notification_interval 120 notification_period 24x7 } line1b line2b line3b line4b address 172.29.16.102 line5a line4a line3a line2a line1a define host{ use generic-host ; Name of host template to use host_name ServerB_172.29.16.103 alias ServerB_172.29.16.103 address 172.29.16.103 check_command check-host-alive max_check_attempts 3 notification_interval 120 notification_period 24x7 } 

Now when we run our command:

$ grep -v "$(grep -B 4 -A 5 'address 172.29.16.102' sample.txt)" sample.txt define host{ use generic-host ; Name of host template to use host_name ServerA_172.29.16.102 alias ServerA_172.29.16.102 address 172.29.16.102 check_command check-host-alive max_check_attempts 3 notification_interval 120 notification_period 24x7 } define host{ use generic-host ; Name of host template to use host_name ServerB_172.29.16.103 alias ServerB_172.29.16.103 address 172.29.16.103 check_command check-host-alive max_check_attempts 3 notification_interval 120 notification_period 24x7 } 
2
  • This works fine for the OP; however, in the general case, there's a problem when the matching lines contain regex reserved characters. Commented Oct 22, 2018 at 0:59
  • This also does not work in the general case where the lines to be removed, those before and after the matched line, are not unique in the file and the others copies should not be removed. Commented Nov 20, 2019 at 16:30
5

This is a perfect example of why sed is the stream editor, and will never replace the power of ex for in-place file editing.

ex -c '/address *172.29.16.103/ ?{?,/}/d x' input 

This command is a simplified form that's not as robust as it could be, but serves for illustration.

The first command finds the regex specified and moves the cursor to that line.

The second command consists of two addresses separated by a comma, upon which the delete command is run. ?{? searches backwards from the current line for an open curly brace, and /}/ searches forward from the current line for a close curly brace. Everything in between is deleted (linewise, so the beginning of the open curly brace line is deleted as well).

x saves the changes and exits. And input is of course the name of the file.

For the input you gave, this command works exactly as desired.


Now, I mentioned this could be improved considerably. We'll start with the regex. The most obvious outness here is that periods are wildcards; the regex as given could also match "172329-16 103". So the periods must be escaped with backslashes so they will only match literal periods.

Next is the whitespace. I put two spaces followed by a * (I could have used \+ but I don't know if that feature is required in POSIX), but what if there are tabs in the file? The best solution is to use [[:space:]]. (This would look much nicer with \+; if anyone finds whether that is POSIX please post a comment.)

Finally, what if the regex is not found in the file? Well, then the file will simply be opened for editing, and the "search" command will fail, and an error message will be printed and the rest of the given commands won't be executed--you'll be left in the ex editor so you can make changes by hand. However, if you want to automate the edits in a script, you probably want the editor to exit if no changes need to be made. The answer is to use the global command, and also to use the -s flag to suppress any output from ex:

ex -sc 'g/address[[:space:]][[:space:]]*172\.29\.16\.103/ ?{?,/}/d x' input 

This isn't quite equivalent to the earlier command; if there is more than one curly brace block with a matching line, the global command here will delete them all. That's probably what you want anyway.

If you want to only delete the first match, while exiting without changing the file if there are no matches at all, you can use the x command as part of the argument to the g command (to exit the file after the first delete command is performed) and throw in a q! command at the bottom in case the g command doesn't execute for lack of any matching lines.

ex -sc 'g/address[[:space:]][[:space:]]*172\.29\.16\.103/ ?{?,/}/d | x q!' input 

To be honest these commands make the process look much more complicated than it is; the robustness comes at the expense of extreme clarity and readability of the code. It's a trade-off.

I recommend editing a few files interactively with ex to get a bit of a feel for it. That way you can see what you're doing. Such an editing session in ex to do this fix interactively looks something like this:

$ ex input "input" 23L, 843C Entering Ex mode. Type "visual" to go to Normal mode. :/103 host_name ServerB_172.29.16.103 :?{?,/}/d # This deletes the current block :$p # Print and move to last line :-5,.p # Print some more lines to check result notification_interval 120 notification_period 24x7 } :?}?+,.d # Trim whitespace } :x # Save and exit $ 

The POSIX specifications for ex provide further reading.

1
  • 1
    Actually piping the commands to ex through the idiom printf '%s\n' 'command 1' 'command 2' | ex filename also avoids the problem of "what if the regex is not found in the file" and it's the cleaner solution that I would now recommend over some of the complicated bits in this answer. Commented Sep 21, 2021 at 5:24
2

Rather than using strict numbers of lines with sed or grep, my preference would be to write something that does basic interpretation of the file format itself, and evaluates the contents of each record. Consider the following using awk:

#!/usr/bin/awk -f function doit(blob) { if (blob !~ /address[[:space:]]+172\.29\.16\.102/) { print blob; } } # Find a new record... /^define host/ { doit(blob); blob=""; } # Don't start each record with a blank line... length(blob) { blob=blob "\n"; } # Collect our data... { blob=blob $0; } END { doit(blob); } 

The idea here is that we'll walk through the file, and each time we see define host we'll start keeping adding each line to the variable blob. When a new record starts, or when we come to the end of the file, we process that variable using the doit() function.

This works in both GNU and classic awk.

0

What about this?

egrep -v '^([[:space:]]+(use|host_name|alias|check_command|max_check_attempts|notification_interval|notification_period)[[:space:]]+|^define host{|^[[:space:]]+})' 

It removes the particular lines that are not needed, if we suppose what there is no similar lines in the file what we should not remove.

0

With sed you can do:

sed -ne'/^[^ ]/!{H;$!d;}' <in >out \ -e 'x;//{/\n *address *172\.29\.16\.103/!p;}' 

...which will buffer each block separately in Hold space as they are read in and only print those which don't contain a match for your pattern.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.