How to edit the entire file after match a grep pattern?
This gets a lot easier if you go through the file backwards. Fortunately, you can do so easily with tac
(which happens to be cat
backwards). We can then use a relatively simple awk
script to look for your host
, and change only its ip
:
$ tac input | awk -v OFS="=" -v myip="changed_address" -v myhost="d" -F"=" '$1 == "host" { if( $2 == myhost ) { sw = "on" } else { sw="off" } } sw == "on" && $1 == "ip" { $2=myip } { print $0 }' | tac
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=changed_address
blahblah
mask=255.0.0.0
host=d
I shall explain in detail how the awk
works:
First, we declare a few variables: one each for the host
and new value for the ip
:
-v myip="changed_address" -v myhost="d"
Further, we declare the field separator for the input and output:
-v OFS="=" -F"="
Now, the actual awk
script itself:
$1 == "host" { // If we see the "host" line..
if( $2 == myhost ) { // And it matches the one we're looking for..
sw = "on" // Set a flag to swap the next IP
} else {
sw="off" // Otherwise, unset the flag
}
}
sw == "on" && $1 == "ip" { // If the flag is set and this is an IP line..
$2=myip // Swap in the new IP
}
{
print $0 // Finally, print out the processed line
}
Once that's all done, we just use tac
again to re-reverse it, making it forwards again.
I know you are expecting something very fast, and simple like a one-line sed command or a smart awk code, but if you don't care...
#!/bin/bash
#Note: Adjusted to run with a posix shell (tested in dash)
filename='file'
newip='127.0.0.1'
hostchar='d'
tac "$filename" | while IFS= read -r line ; do
case $line in
host=${hostchar})
flag=on
;;
host=*)
flag=off
;;
esac
if [ "$flag" = "on" ]; then
case $line in
ip=*)
echo ip=$newip
continue
;;
#you can replace more variables at once by adding it here
#in the same standard.
#for ex: mask=*) echo mask=$newmask; continue ;; etc...
esac
fi
echo $line
done | tac
Results:
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=127.0.0.1
blahblah
mask=255.0.0.0
host=d
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 withip
. I think this might not work with all implementations ofsed
, so if yours doesn't support this, replace thesed
command withperl -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 stringhost=c
and then another newline, then replaceip=
and 1 or more non-whitespace characters (\S+
) following it withip=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