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

Hello all,

I've been reading the PM write-ups on Tie::File (All hail Dominus!), and they've been great, but I still can't figure out how to conditionally insert lines into a file if they don't exist.

The script I'm writing is supposed to look for server entries, placing them at the end of the file if they're absent or replacing them if incorrect. I've got the replacing part going great. That was easy. However, if the entries aren't present, I need to add them.

How should I go about checking for these entries? Let's say they should look something like this:

server1=myhost1.mydomain.com
server2=myhost2.mydomain.com

I'm sure there's an answer that's stupidly simple and I'm just not seeing it. I really can't make up my mind whether I should be using an array function or if I should be using something from Tie::File itself. Or perhaps there's an entirely different method of which I'm not even aware.

Thanks for any help you can give.

Dev Goddess
Developer / Analyst / Criminal Mastermind

"Size doesn't matter. It's all about speed and performance."

  • Comment on Inserting lines in a file with Tie::File if lines don't exist

Replies are listed 'Best First'.
Re: Inserting lines in a file with Tie::File if lines don't exist
by pg (Canon) on Dec 31, 2003 at 01:24 UTC

    grep can help. It actually helps you in both replace and search (before you do insert). Read perlfunc for details.

    use warnings; use strict; my @a = ("a=1", "b=2", "c=3"); for my $e (grep /^b=/, @a) { $e = "b=22"; } print join(",", @a);

    The other way is to use hash, as you are handling key-value pairs.

    Update:

    See my reply to devgoddess's reply. I made a general purpose sub there.

      Hmmm... You mean I should put the array into a hash? That just sounds like it's a lot more complicated than it needs to be. I simply need to check the @lines array created with Tie::File for those entries and, if they're not present, add them to the end of the array... hence, the end of the file.

      Can you explain a little more what you mean about using a hash?

      Dev Goddess
      Developer / Analyst / Criminal Mastermind

      "Size doesn't matter. It's all about speed and performance."

        It is fine to stay with array. Hope the following code helps:

        Made a general purpose function to handle both replace and insert, and determines whether to insert or replace on its own:

        use warnings; use strict; my @a = ("a=1", "b=2", "c=3"); print join(",", @a), "\n"; add("b", 22);#this replaces print join(",", @a), "\n"; add("d", 45);#this inserts print join(",", @a), "\n"; sub add { my ($key, $val) = @_; my $exists = 0; for (grep /^$key=/, @a) { $_ = "$key=$val"; $exists = 1; } if (!$exists) { push @a, "$key=$val" } }
Re: Inserting lines in a file with Tie::File if lines don't exist
by strider corinth (Friar) on Dec 31, 2003 at 18:02 UTC
    I'm not entirely clear on what you're asking, but probably the two functions you want help with are grep and splice. Grep allows you to search the file for a missing line, and splice will allow you to insert it wherever you like, because the author of Tie::File included an interface for it.

    --
    Love justice; desire mercy.
Managing config files
by linkeditor (Initiate) on Dec 31, 2003 at 21:43 UTC

    Not for the low level problem of inserting lines in a file, but for the general purpose of managing a configuration file, you might also consider using a dedicated module.

    A quick search on CPAN leads for example on those ones :

    Parse::PlainConfig may be the most interesting in this case, as it allows both to write the configuration file and to store lists (were they of servers or of something else) and even hashes.

      Another solution, taking back the idea of a simple key=value file, using a hash, but directly reading from and writing to file, without using File::Tie neither another existing module.


      # SimplestConfig.pm # SimplestConfig->new('filename') returns an object whose attributes # (simple values only at this time) will be stored in a file named # filename under the form attribute=value and as such may be usefull # to manage simple config files. use strict; use warnings; package SimplestConfig; sub new { my ($class, $filename) = @_; my $self = {_FileName_ => $filename}; bless $self, $class; if (open CFG, "<$filename") { while (<CFG>) { /^\s*(\S.*?)\s*=\s*(.*?)\s*$/ and $self->{$1} = $2; } close CFG; } else { warn "Can't read $filename, creating it\n"; } return $self; } sub DESTROY { my ($self) = @_; open CFG, ">$self->{_FileName_}" or die "Can't write $self->{_FileName_}\n"; delete $self->{_FileName_}; print CFG map "$_=$self->{$_}\n", sort keys(%{$self}); close CFG; } 1;

      #!/usr/bin/perl -w # test1.pl use strict; use lib "$ENV{HOME}/perl"; use SimplestConfig; my $cfg = SimplestConfig->new("$ENV{HOME}/test.cfg"); $cfg->{a} = 1; $cfg->{b} = 2; $cfg->{c} = 3;

      #!/usr/bin/perl -w # test2.pl use strict; use lib "$ENV{HOME}/perl"; use SimplestConfig; my $cfg = SimplestConfig->new("$ENV{HOME}/test.cfg"); $cfg->{b} = 20; $cfg->{d} = 4;

      Test:

      $ more test.cfg test.cfg: No such file or directory $ ./test1.pl Can't read /home/laurent/test.cfg, creating it $ more test.cfg a=1 b=2 c=3 $ ./test2.pl $ more test.cfg a=1 b=20 c=3 d=4

      Note it would discard any line not of the form key=value from an existing file.

      And on another subject, Happy New Year to everybody.

Re: Inserting lines in a file with Tie::File if lines don't exist
by devgoddess (Acolyte) on Jan 04, 2004 at 01:14 UTC
    Hello all,

    Again, thank you all so much for the code and the tips. I'm still working on the code (not actively, or it would have been done by now, but I was ill the last few days), and I'd like to do a write-up on it when it's done, if you guys think the code's good enough. ;) I'll post it when I've got it all tweaked.

    Oh, one more thing... Any idea why I get a syntax error using Tie::File for the line

    tie @lines, 'Tie::File', filename or die ...;

    When I take off "or die ...;" it works fine. Of course, this isn't desirable. If it can't find the file, I want it to give an error message and die.

    Later. :)

    Dev Goddess
    Developer / Analyst / Criminal Mastermind

    "Size doesn't matter. It's all about speed and performance."

      Could you copy and paste thet actual line of code? Because currently, it looks just peachy.


      Who is Kayser Söze?
      Code is (almost) always untested.
        Sure thing.

        tie @lines, 'Tie::File', $hostsfile or die ...;

        Dev Goddess
        Developer / Analyst / Criminal Mastermind

        "Size doesn't matter. It's all about speed and performance."