in reply to Trying to create a linux interfaces file out of rows from a database.

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

Replies are listed 'Best First'.
Re^2: Trying to create a linux interfaces file out of rows from a database.
by jduffany (Initiate) on Jul 14, 2011 at 00:49 UTC

    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

        OK, I didn't know how to look up references without knowing they existed! I was doing Google searches for strings like this: '@{$}' & getting nothing back. (Duh!)

        I'm still digesting, but it's much clearer now. I guess this proves that I need to keep hitting the proverbial books. It did seem to me that having just scalars, arrays & hashes to store info was very limiting, but I figured there were workarounds. References seem less like workarounds, and more like shortcuts. Very nice! I'm having a great time learning this stuff, thanks a lot GrandFather!