jduffany has asked for the wisdom of the Perl Monks concerning the following question:

I've almost got it, but I can't get the 'auto' line right. I think this is a pretty no brainer question, but alas,I am a noob.

auto lo iface lo inet loopback auto eth0 <--This line needs eth1, too! iface eth0 inet dhcp iface eth1 inet static address 192.168.0.4 netmask 255.255.255.0 network 192.168.0.0 broadcast 192.168.0.255 gateway 192.168.0.1

This is my table select output

my $sth = $dbh->prepare( "SELECT host, name, type, address, netmask, +gateway FROM $table_name" ); #(PRINTED) app17, eth1, static, 192.168.0.2, 255.255.255.0, 192.168.0.1 app15, eth0, dhcp, , , app15, eth1, static, 192.168.0.4, 255.255.255.0, 192.168.0.1 app12, eth1, static, 192.168.0.5, 255.255.255.0, 192.168.0.1

My first instinct was to create a hash keyed off of hostname, but there's duplicates, then I thought IP address, but those that are DHCP don't have them.

Anyway here's some of the code:

my @ifaces; my @hosts; while ( my @row = $sth->fetchrow_array ) { my $host = $row[0]; my $iface = $row[1]; my $type = $row[2]; my $address = $row[3]; my $netmask = $row[4]; my $gateway = $row[5]; push( @hosts, "$host" ); ########### Here's what I can't get right ############# while ( @hosts ) { push( @ifaces, "$iface"); @ifaces = pop( @ifaces ); last; } ########### Above is what I can't get right ############# my $tmp_file = "/tmp/interfaces.$host"; my $loopback_string = "auto lo\niface lo inet loopback\n\nauto"; open(my $interfaces_file,"+>>$tmp_file") or die "Unable to open $t +mp_file: $!\n"; if ( -z $tmp_file ) { print {$interfaces_file} "$loopback_string @ifaces\n\n"; } if ($type eq "dhcp") { print {$interfaces_file} "iface $iface inet $type\n\n"; } else { print {$interfaces_file} "iface $iface inet $type\naddress $ad +dress\nnetmask $netmask\nnetwork 192.168.0.0\nbroadcast 192.168.0.255 +\ngateway $gateway\n"; close($interfaces_file) or die "Can't close it\n"; } } $dbh->disconnect;

Replies are listed 'Best First'.
Re: Trying to create a linux interfaces file out of rows from a database.
by GrandFather (Saint) on Jul 09, 2011 at 01:14 UTC

    Near as I can tell you are hoping to create a header for each file, then populate the auto line, then populate the rest of the file. However that is a rather fragile concatenation of circumstances! There are two approaches that are likely to work better for you: modify your select so you get the host rows together (my $sth = $dbh->prepare("SELECT host, name, type, address, netmask, gateway FROM $table_name order by host");), or make a preliminary pass over the data, then create the files in a second pass. The second approach is shown first:

    use strict; use warnings; my @data = ( ['app12', 'eth1', 'static', '192.168.0.5', '255.255.255.0', '192.1 +68.0.1'], ['app15', 'eth0', 'dhcp', '', '', ''], ['app15', 'eth1', 'static', '192.168.0.4', '255.255.255.0', '192.1 +68.0.1'], ['app17', 'eth1', 'static', '192.168.0.2', '255.255.255.0', '192.1 +68.0.1'], ); my %hosts; while (my @row = @{shift @data || []}) { push @{$hosts{$row[0]}}, \@row; } for my $host (keys %hosts) { genFile ($host, $hosts{$host}); } sub genFile { my ($host, $rows) = @_; my $fileName = "/tmp/interfaces.$host"; my @ifaces = map {$_->[1]} @$rows; print "--------- $fileName --------------\n"; print <<STR; auto lo iface lo inet loopback auto @ifaces STR for my $row (@$rows) { my ($hostname, $iface, $type, $address, $netmask, $gateway) = +@$row; if ($type eq "dhcp") { print "\niface $iface inet $type\n"; } else { print <<STR; iface $iface inet $type address $address netmask $netmask network 192.168.0.0 broadcast 192.168.0.255 gateway $gateway STR } } }

    Prints:

    --------- /tmp/interfaces.app15 -------------- auto lo iface lo inet loopback auto eth0 eth1 iface eth0 inet dhcp iface eth1 inet static address 192.168.0.4 netmask 255.255.255.0 network 192.168.0.0 broadcast 192.168.0.255 gateway 192.168.0.1 --------- /tmp/interfaces.app12 -------------- auto lo iface lo inet loopback auto eth1 iface eth1 inet static address 192.168.0.5 netmask 255.255.255.0 network 192.168.0.0 broadcast 192.168.0.255 gateway 192.168.0.1 --------- /tmp/interfaces.app17 -------------- auto lo iface lo inet loopback auto eth1 iface eth1 inet static address 192.168.0.2 netmask 255.255.255.0 network 192.168.0.0 broadcast 192.168.0.255 gateway 192.168.0.1

    The alternate approach uses the same output sub, but alters the way the data is managed up front. This code is slightly more complicated, but will work for huge databases where collating all the data in a hash is not practical - although it's hard to imagine when that would be an issue:

    my @hostRows; while ((my @row = @{shift @data || []}) || @hostRows) { if (@hostRows && (! @row || $row[0] ne $hostRows[0][0])) { genFile ($hostRows[0][0], \@hostRows); @hostRows = (); next if ! @row; } push @hostRows, \@row; }

    This code replaces the main line code following the data in the first version. The files generated are the same with the same content, although they will most likely be generated in a different order.

    True laziness is hard work

      Wow! Fantastic! Works like a charm. I'm humbled by your Perlness. Thanks lots, now I just need to figure out what you did. To get me started could you explain the first line:

      push @{$hosts{$row[0]}}, \@row;

      I'm just trying to learn on my own & can't find any reference on the web regarding that syntax (I know I'm just not looking in the right places). Again, thanks GrandFather, you gave me a good, healthy bite to chew on.

        This first thing to be aware of is than in Perl we make interesting data structures by building them out of either an array or a hash using references to nested arrays or hashes as the values. So your first port of call is to References quick reference to help figure out the syntax for using references. Come back here when you have digested that.

        Ok, now you should be able to figure out that @{$hosts{$row[0]}} dereferences a hash that stores references to arrays as its values. $row[0] is the host name and we are using that as the key to the hosts hash - seems appropriate. The push pushes a reference (that's what the \ in front of @row gives us) to @row (which contains a row of data for a particular interface on a host) into the array associated with the host name. Our %hosts hash is a hash of arrays of arrays!

        To generate the individual host files we iterate over the key values in the %hosts hash and for each key (which is a host name remember) we call genFile passing the host name and a reference to an array containing all the rows we got for the host.

        In genFile we use map {$_->[1]} @$rows; to generate a list of interfaces then use that in the header part of the file. The for loop then creates the entries for each interface.

        True laziness is hard work