Remove zone block from wildcard binding configuration file from command line
I am trying to figure out how to make a script that will delete a specific zone in my Bind zone file.
I have about 200 domains in my zone file and I thought I could make a script that would delete a specific domain for me. On my master server, the zone file looks like this:
zone "example.com" {
type master;
file "master/example.com";
};
zone "example.net" {
type master;
file "master/example.net";
};
However, my slave has two end brackets instead of one.
zone "example.com" {
type slave;
file "slave/example.com";
masters {
10.0.0.1;
};
};
zone "example.net" {
type slave;
file "slave/example.net";
masters {
10.0.0.1;
};
};
So I could technically grep example.com, take that line and every line before, };
and delete. The same goes for the slave only there, it will have to delete every line up to the second};
This will work with the master:
sed -n -e '/\"example.net\"/,/\};/p' zones.local
But not on a slave. Does anyone know how to fix this? The zoned file looks the same and there are no comments or anything like that.
source to share
I would use Perl for my recursive regexes. We can use them according to the curly brace block, from the open parenthesis to the corresponding closing parenthesis. For example:
perl -i -0777 -pe 's/zone\s*"example.net"\s*(\{([^{}]|(?1))*\});\s*//g' foo.conf
The tricky part of the regular expression (\{([^{}]|(?1))*\})
. This is captured as capturing group 1, and is recursively recursively referenced within itself as (?1)
, so it matches a group enclosed in curly braces in an arbitrary number of many unbound and curly brace nested groups.
As long as there are no parentheses in comments or lines, this should survive all your formatting changes.
Update: I had to look a bit, but there are BIND file parsers out there that offer a more robust solution to the problem - ultimately, regexes (even recursive ones) can only carry you so far, and in some special cases. In general, it is better to use the correct parsers for structured data like this, even if the code gets longer for it. For example, with the BIND :: Config :: Parser module, you can write
#!/usr/bin/perl
use BIND::Config::Parser;
my $parser = new BIND::Config::Parser;
my $indent = 0;
my $ignoredepth = 0;
my $file = $ARGV[0];
my $domain = "\"$ARGV[1]\"";
$parser->set_open_block_handler(
sub {
if(($_[0] eq "zone" && $_[1] eq $domain) || $ignoredepth != 0) {
++$ignoredepth;
} else {
print " " x $indent, join(" ", @_), " {\n";
++$indent;
}
});
$parser->set_close_block_handler(
sub {
if($ignoredepth != 0) {
--$ignoredepth;
} else {
--$indent;
print " " x $indent, "};\n";
}
});
$parser->set_statement_handler(
sub {
if($ignoredepth == 0) {
print " " x $indent, join(" ", @_), "\;\n";
}
});
$parser->parse_file($file);
and call
perl remove_zone.pl named.conf.local example.net
This will be a nuke comment, but keep the functional parts of the config file intact, no matter what the regexp might confuse there. This is the best way for automatic use.
source to share
This zone file obeys Tcl syntax, so we can define a procedure named "zone" and read the zone file as if it were a Tcl script:
#!/usr/bin/env tclsh lassign $argv domain_to_remove zone_file proc zone {domain body} { if {$domain ne $::domain_to_remove} { puts [format {zone "%s" {%s}} $domain $body] } } source $zone_file
and then
$ tclsh remove_zone.tcl example.com zones zone "example.net" { type slave; file "slave/example.net"; masters { 10.0.0.1; }; }
source to share
sed is NOT for anything involving multiple lines, for simple single line substitutions. All seds Thai language constructs for handling multiple input lines at the same time became obsolete in the mid-1970s when awk was invented, in the past time to let them go:
$ awk -v RS= -v ORS='\n\n' '/example.com/' file
zone "example.com" {
type master;
file "master/example.com";
};
With all your samples in one file:
$ cat file
zone "example.com" {
type master;
file "master/example.com";
};
zone "example.net" {
type master;
file "master/example.net";
};
zone "example.com" {
type slave;
file "slave/example.com";
masters {
10.0.0.1;
};
};
zone "example.net" {
type slave;
file "slave/example.net";
masters {
10.0.0.1;
};
};
...
$ awk -v RS= -v ORS='\n\n' '/example.com/' file
zone "example.com" {
type master;
file "master/example.com";
};
zone "example.com" {
type slave;
file "slave/example.com";
masters {
10.0.0.1;
};
};
...
$ awk -v RS= -v ORS='\n\n' '/example.net/' file
zone "example.net" {
type master;
file "master/example.net";
};
zone "example.net" {
type slave;
file "slave/example.net";
masters {
10.0.0.1;
};
};
source to share