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

Monks,

I am having a few problems with my script for editing postfix files. It looks a bit on the messy side and i was wondering if any of you could think of a cleaner way of doing the below. Here Is The Code:
my $datafile = '/etc/postfix/virtual'; my $req_addr = "$username\@$domain"; my ( $name, $domain ) = $req_addr =~ /^([\w.-]+)(@[\w.-]+)$/ ; open FH, $datafile or die; my @virtual = <FH> ; close FH ; open FH, ">$datafile" or die; my $wrote_it = 0 ; for ( @virtual ) { print FH $_ ; if ( $_ =~ /$domain/ && !$wrote_it ) { print FH "$req_addr\t$username\n" ; $wrote_it++ ; } } close FH ;
Here Is The File:
test.com VIRTUALDOMAIN #defines the domain name @test.com jnapier #defines the postmaster of domain idunno@test.com idunno #just a regular user other.com VIRTUALDOMAIN #defines the domain name @other.com jnapier #defines the postmaster of domain iknow@other.com iknow #just a regular user
I have the following variables $username which contains the users name (in this case idunno, or iknow), and $domain (in this case test.com or other.com).

The script needs to open the /etc/postfix/virtual file, using whats in $domain, seek to the line that defines the domain name, move down past the line that defines the postmaster of the domain, and dump $username\@$domain\t$username just below that, so it basicaly adds another regular user.

Thanks

Replies are listed 'Best First'.
Re: There has got to be a Cleaner Way!
by FoxtrotUniform (Prior) on Aug 31, 2002 at 18:34 UTC

    Two comments:

    1. Check the return values of close calls as well as open. You'll probably also want to print $! in the message.
    2. I always keep system files in an RCS repository. This has saved my butt only once, but when it did, I was most grateful. You could use the Rcs module, but it's probably overkill; something like
      system("ci -l -m 'adding user $username\@$domain' $datafile") == 0 or die "RCS check-in of $datafile failed: $?\n";
      should suffice. (Note: haven't tested the error checking; I just pulled that out of perldoc -f system.)

    --
    F o x t r o t U n i f o r m
    Found a typo in this node? /msg me
    The hell with paco, vote for Erudil!

      how would you go about doing the above? there must be a better way than the way i am using
Re: There has got to be a Cleaner Way!
by Arien (Pilgrim) on Aug 31, 2002 at 19:05 UTC

    Have a look at Tie::File (including its autochomping and locking).

    — Arien

Re: There has got to be a Cleaner Way!
by BrowserUk (Patriarch) on Sep 01, 2002 at 00:28 UTC

    Not knowing what an 'RCS' is or does, and if, as I suspect, this is intended to run as a cron job, adding one new user at a time, then if I were tackling this problem I might set out to do it like this.

    Pseudo code.

    1. Open the file rw.
    2. Read until I find the line that starts with the domain of interest.
    3. Read on until I found a line that does not contain the domain of interest remembering position where each line started till I find it.
    4. Once I found it, I know where that line started, append the rest of the file onto the end of the string that has it in.
    5. Rewind to the saved position.
    6. Write the new line.
    7. Re-write the rest.
    8. Close and done.

    Perl code

    my $datafile = '/etc/postfix/virtual'; =pod # This bits a mess? # First you composite the username and domain together my $req_addr = "$username\@$domain"; # Then you use a very iffy regex to break them apart? my ( $name, $domain ) = $req_addr =~ /^([\w.-]+)(@[\w.-]+)$/ ; =cut # so assuming that $name and $domain contain the right information my ($line, $mark, $rest); open FH, "+<$datafile" or die "Open $main::FH failed; $!\n"; #1 $line = <FH> while $line !~ /^$domain$/o; #2 $mark = tell(FH), $line = <FH> while $line =~ /$domain/o; #3 @rest = ($line, <FH>); #4 truncate( FH, $pos ); #5 print FH "$name\@$domain\t$name\n"; #6 print FH "@rest"; #7 close FH or die warn "Error closing $main::FH; $!\n"; #8

    Warning! However, if anything should go wrong whilst your processing, your datafile could end up corrupted.

    A better way of doing this would be to use the -spi switches from perlman:perlrun

    Something like this

    #! perl -wspi.bak BEGIN{ die <<USAGE unless defined($domain) & $domain ne '1' && defined($ +name) & $name ne '1'; Usage: $0 -spi -name=fred --domain=some.com path/to/userfile USAGE my ($started, $finished) = (0,0); } next if $finished; # Just print the rest once +we've done the biz $started = /^$domain/o # look for the start of the + right block or next unless $started; # and just print till we f +ind it $finished = !/$domain/o # Continue just printing ti +ll we find the end or next unless $finished; print "$name\@$domain\t$name\n"; # Then print the new bit END{ unlink $main::ARGV . $^I or warn "Failed to delete backup $main::ARGV$^I; $!\n" }

    Which works quite nicely on my system using your sample file format.

    The caveat with this (and the preceding and yours) is that if the same user registers several times--as users are apt to do if they don't see immediate results--you will end up with multiple lines for that user.

    Fixing this limitation is left AAEFTP.


    Well It's better than the Abottoire, but Yorkshire!
      Thanks for that. I already have the 'multiple instance of same user' thing sorted.
      Because the accounts rely on a unix username, i run the adduser command before making any other mod's, and if it returned that the user already exists, then i break from the code and send the user an e-mail to tell them that the account is taken.