Reputation: 21
I have a large firewall configuration file with sections like these distributed all over:
edit 78231
set srcintf "port1"
set dstintf "any"
set srcaddr "srcaddr"
set dstaddr "vip-dnat"
set service "service"
set schedule "always"
set logtraffic all
set logtraffic-start enable
set status enable
set action accept
next
I want to replace value "port1"
, which is 3 lines above search string "vip-dnat"
.
It seems the below solution is close but I don't seem to be able to invert the search to check above the matched string. Also it does not replace the value inside the file: Replace nth line below the searched pattern in a file
I'm able to extract the exact value using the following awk command but simply cannot figure out how to replace it within the file (sub
/gsub
?):
awk -v N=3 -v pattern=".*vip-dnat.*" '{i=(1+(i%N));if (buffer[i]&& $0 ~ pattern) print buffer[i]; buffer[i]=$3;}' filename
"port1"
Upvotes: 2
Views: 76
Reputation: 203229
Whenever you have tag-value pairs in your data it's best to first create an array of that mapping (tag2val[]
below) and then you can test and/or change and/or print the values in whatever order you like just be using their names:
$ cat tst.awk
$1 == "edit" { editId=$2; next }
editId != "" {
if ($1 == "next") {
# Here is where you test and/or set the values of whatever tags
# you like by referencing their names.
if ( tag2val[ifTag] == ifVal ) {
tag2val[thenTag] = thenVal
}
print "edit", editId
for (tagNr=1; tagNr<=numTags; tagNr++) {
tag = tags[tagNr]
val = tag2val[tag]
print " set", tag, val
}
print $1
editId = ""
numTags = 0
delete tag2val
}
else {
tag = $2
sub(/^[[:space:]]*([^[:space:]]+[[:space:]]+){2}/,"")
sub(/[[:space:]]+$/,"")
val = $0
if ( !(tag in tag2val) ) {
tags[++numTags] = tag
}
tag2val[tag] = val
}
}
$ awk -v ifTag='dstaddr' -v ifVal='"vip-dnat"' -v thenTag='srcintf' -v thenVal='"foobar"' -f tst.awk file
edit 78231
set srcintf "foobar"
set dstintf "any"
set srcaddr "srcaddr"
set dstaddr "vip-dnat"
set service "service"
set schedule "always"
set logtraffic all
set logtraffic-start enable
set status enable
set action accept
next
Note that the above approach:
Upvotes: 2
Reputation: 133458
We could use tac
+ awk
combination here. I have created a variable occur
with value after how many lines(when "vip-dnat"
is found) you need to perform substitution.
tac Input_file |
awk -v occur="3" -v new_port="new_port_value" '
/\"vip-dnat\"/{
found=1
print
next
}
found && ++count==occur{
sub(/"port1"/,new_port)
found=""
}
1' |
tac
Explanation: Adding detailed explanation for above.
tac Input_file | ##Printing Input_file content in reverse order, sending output to awk command as an input.
awk -v occur="3" -v new_port="new_port_value" ' ##Starting awk program with 2 variables occur which has number of lines after we need to perform substitution and new_port what is new_port value we need to keep.
/\"vip-dnat\"/{ ##Checking if line has "vip-dnat" then do following.
found=1 ##Setting found to 1 here.
print ##Printing current line here.
next ##next will skip all statements from here.
}
found && ++count==occur{ ##Checking if found is SET and count value equals to occur.
sub(/"port1"/,new_port) ##Then substitute "port1" with new_port value here.
found="" ##Nullify found here.
}
1' | ##Mentioning 1 will print current line and will send output to tac here.
tac ##Again using tac will print output in actual order.
Upvotes: 3
Reputation: 12347
Use a Perl one-liner. In this example, it changes line number 3 above the matched string to set foo bar
:
perl -0777 -pe 's{ (.*\n) (.*\n) ( (?:.*\n){2} .* vip-dnat ) }{${1} set foo bar\n${3}}xms' in_file
Prints:
edit 78231
set foo bar
set dstintf "any"
set srcaddr "srcaddr"
set dstaddr "vip-dnat"
set service "service"
set schedule "always"
set logtraffic all
set logtraffic-start enable
set status enable
set action accept
next
When you are satisfied with the replacement written into STDOUT, change perl
to perl -i.bak
to replace the file in-place.
The Perl one-liner uses these command line flags:
-e
: Tells Perl to look for code in-line, instead of in a file.
-p
: Loop over the input one line at a time, assigning it to $_
by default. Add print $_
after each loop iteration.
-i.bak
: Edit input files in-place (overwrite the input file). Before overwriting, save a backup copy of the original file by appending to its name the extension .bak
.
-0777
: Slurp files whole.
(.*\n)
: Any character, repeated 0 or more times, ending with a newline. Parenthesis serve to capture the matched part into "match variables", numbered $1
, $2
, etc, from left to right according to the position of the opening parenthesis.
( (?:.*\n){2} .* vip-dnat )
: 2 lines followed by the line with the desired string vip-dnat
. (?: ... )
represents non-capturing parentheses.
SEE ALSO:
perldoc perlrun
: how to execute the Perl interpreter: command line switches
perldoc perlre
: Perl regular expressions (regexes)
perldoc perlre
: Perl regular expressions (regexes): Quantifiers; Character Classes and other Special Escapes; Assertions; Capture groups
perldoc perlrequick
: Perl regular expressions quick start
The regex uses these modifiers:
/x
: Ignore whitespace and comments, for readability.
/m
: Allow multiline matches.
/s
: Allow .
to match a newline.
Upvotes: 2