简体   繁体   中英

Replace value in nth line ABOVE search string using awk/sed

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"

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.

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.

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:

  1. Will work even if/when either of the values you want to find appear in other contexts (eg associated with other tags or as substrings of other values), and doesn't rely on how many lines are between the lines you're interested in or what order they appear in in the record.
  2. Will let you change that value of any tag based on the value of any other tag and is easily extended to do compound comparisons, assignments, etc. and/or print the values in a different order or print a subset of them or add new tags+values.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM