How to edit the entire file after match a grep pattern

awkgrepsedtext processing

To simplify, I want to edit the whole file after matched pattern, example of a file:

$ cat file
ip=x.x.x.a
mask=255.0.0.0
host=a
ip=x.x.x.b
mask=255.0.0.0
host=b
ip=x.x.x.c
mask=255.0.0.0
host=c
ip=x.x.x.x
blahblah
mask=255.0.0.0
host=d

Let's suppose that I want to edit the IP from host c, but note that this may be variable, so I don't know this value. If I grep host c and use the -B2 to print the lines I can't edit it in the original file! Another point the line may not have the same structure, that's the case of host d, between ip and mask info there's some text, so I can't assume that the IP pattern always will be 2 lines before my search pattern.

Resuming, I can't grep the IP directly because I don't know it, instead I need to search for host, and edit the line before this match to change the value of IP. How can I do this?

Best Answer

This will change the IP associated with host c to 1.2.3.4:

$ sed 's/^ip/\nip/' file | perl -00pe 'if(/\nhost=c\n/){s/ip=\S+/ip=1.2.3.4/} s/\n\n/\n/' 
ip=x.x.x.a
mask=255.0.0.0
host=a
ip=x.x.x.b
mask=255.0.0.0
host=b
ip=1.2.3.4
mask=255.0.0.0
host=c
ip=x.x.x.x
blahblah
mask=255.0.0.0
host=d

Explanation:

  • sed 's/^ip/\nip/' file : add an extra newline (\n) to each line beginning with ip. I think this might not work with all implementations of sed, so if yours doesn't support this, replace the sed command with perl -pe 's/^ip/\nip/'. We need this in order to use Perl's "paragraph mode" (seen below).

  • perl -00pe : the -00 makes perl run in "paragraph mode" where a "line" is defined by two consecutive newlines. This enables us to treat each host's block as a single "line". The -pe means "print each line after applying the script given by -e to it".

  • if(/\nhost=c\n/){s/ip=\S+/ip=1.2.3.4/} : if this "line" (section) matches a newline followed by the string host=c and then another newline, then replace ip= and 1 or more non-whitespace characters (\S+) following it with ip=1.2.3.4.

  • s/\n\n/\n/ replace each pair of newlines with a single newline to get the original file's format back.

If you want this to change the file in place, you can use:

tmp=$(mktemp); sed 's/^ip/\nip/' file > $tmp; 
perl -00pe 'if(/\nhost=c\n/){s/ip=\S+/ip=1.2.3.4/} s/\n\n/\n/' $tmp > file
Related Question