I'm try to edit a FreeBSD rc.conf file. The data part of the file is made of key value pairs like:
There can also be comments and empty lines. What I need to be able to do is update values from "YES" to "NO"
or add new key/value pairs to the end of the file without disturbing the comments or the empty lines. The problem with my code
is that it duplicates the file in my new file and I'm not sure why.
This is what I have so far and a test file:
package Init::Freebsd::RCConf::Line;
use strict;
use warnings;
use Class::Std;
{
my %raw_line : ATTR( :get<raw_line> :set<raw_line> );
my %key : ATTR( :get<key> :set<key> );
my %value : ATTR( :get<value> :set<value> );
sub BUILD {
my ( $self, $ident, $args_ref ) = @_;
$raw_line{$ident} = $args_ref->{'line'} || '';
$self->parse();
}
sub parse {
my $self = shift;
if ( $self->get_raw_line() =~ /=/) {
my ( $key, $value ) = split( /=/, $self->get_raw_line(), 2
+ );
$self->set_key($key);
$self->set_value($value);
}
else {
$self->set_key('');
$self->set_value( $self->get_raw_line() );
}
}
sub print_line {
my $self = shift;
if ( $self->get_key() ne '' ) {
return sprintf( "%s=%s", $self->get_key(), $self->get_valu
+e() );
}
else {
return $self->get_value();
}
}
}
1;
package Init::Freebsd::RCConf;
use strict;
use warnings;
use Carp;
use Fcntl qw(:flock);
use FindBin;
use Init::Freebsd::RCConf::Line;
use File::Copy;
use Class::Std;
{
my %file : ATTR( :get<file> :set<file> );
my %file_name : ATTR( :get<file_name> :set<file_name> );
sub BUILD {
my ( $self, $ident, $args_ref ) = @_;
$file_name{$ident} = (-f '/etc/rc.conf') ? '/etc/rc.conf' : $F
+indBin::Bin . '/rc.conf';
$file{$ident} = [];
$self->load();
}
sub add_line : RESTRICTED {
my $self = shift;
my $line = shift;
my $ident = ident($self);
push @{ $file{$ident} }, $line;
}
sub load {
my $self = shift;
my $ident = ident($self);
open my $read_fh, '<', $self->get_file_name() or croak('Can no
+t find ' . $self->get_file_name() . ': ' . $!);
flock($read_fh, LOCK_SH);
while ( <$read_fh> ) {
chomp;
#print $_ . "\n";
my $line = Init::Freebsd::RCConf::Line->new( { line => $_
+} );
$self->add_line($line);
}
close($read_fh);
}
sub set_value {
my $self = shift;
my $key = shift;
my $value = shift;
my $ident = ident($self);
$key .= '_enable';
$value = sprintf(qq{"%s"}, $value);
for my $line (@{ $file{$ident} }) {
if ( $line->get_key() eq $key ) {
$line->set_value($value);
return;
}
}
# If we've made it this far, it must be new line and
# will be added to the end of the file
my $new_line = Init::Freebsd::RCConf::Line->new();
$new_line->set_key($key);
$new_line->set_value($value);
$self->add_line($new_line);
}
sub write_file {
my $self = shift;
my $ident = ident($self);
my $temp_file = '/tmp/temp.rc.conf';
open my $write_fh, '>', $temp_file or die 'Cannot open '. $tem
+p_file . ': ' . $!;
flock($write_fh, LOCK_EX);
for my $line ( @{ $file{$ident} } ) {
print {$write_fh} $line->print_line() . "\n";
}
close($write_fh);
File::Copy::copy($temp_file, $self->get_file_name()) or croak
+'Unable to copy file ' . "$!";
unlink $temp_file if (-s $self->get_file_name() );
}
}
1;
#!perl
use Test::More tests => 8;
use FindBin;
use Init::Freebsd::RCConf;
my @methods = qw{ load add_line set_value write_file };
my $file_name = $FindBin::Bin . '/rc.conf2';
build_conf($file_name);
my $conf = Init::Freebsd::RCConf->new();
$conf->set_file_name($file_name);
isa_ok( $conf, 'Init::Freebsd::RCConf' );
can_ok( $conf, $_ ) for (@methods);
$conf->load();
$conf->set_value( 'sshd', 'NO' );
$conf->write_file();
my $sshd = look_for( $file_name, 'sshd' );
ok( $sshd =~ /NO/, 'SSH set to "NO"' );
$conf->set_value( 'ssl-something', 'YES' );
$conf->write_file();
my $ssl = look_for( $file_name, 'ssl-something' );
ok( $ssl =~ /YES/, 'ssl-something set to YES' );
$conf->set_value( 'ssl-something', 'NO' );
$conf->write_file();
my $ssl = look_for( $file_name, 'ssl-something' );
ok( $ssl =~ /NO/, 'ssl-something set to NO' );
unlink($file_name);
######################################################################
+#########
sub build_conf {
my $file_name = shift;
open( my $file, '>', $file_name ) or die 'Cannot open file' . "\n"
+;
for my $line (<DATA>) {
print {$file} $line;
}
close $file;
}
sub look_for {
my $file_name = shift;
my $service_name = shift;
open( my $file, '<', $file_name ) or die 'Cannot open file' . "\n"
+;
for my $line (<$file>) {
return $line if $line =~ /$service_name/;
}
}
__DATA__
#This is a example file.
sshd_enable="YES"
named_enable="YES"
pureftpd_enable="YES"
mysql_enable="YES"
Thanks for any help.