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

Hi Monks,

I am working on a script to edit the Postfix MTA file. Its a little different from most because it doesn't contain a postmaster.
The file is layed out like the following:
aaaa.com VIRTUALDOMAIN bbbb.com VIRTUALDOMAIN cccc.com VIRTUALDOMAIN dddd.net VIRTUALDOMAIN
I have $dom which contains a domain name (i.e aaaa.com), the script needs to search for the line in the file above (called /etc/postfix/virtual) move down to the line below it and print $user\@$dom\t$user.

I know this is not rocket science, but its part of a larger script that i am writing, and i just can't get to grips with this bit.

Cheers Guys

Replies are listed 'Best First'.
Re: Basic Seek
by kschwab (Vicar) on Sep 08, 2002 at 16:11 UTC
    If I understand this correctly...
    # open the input file open(IN,"file") or die; # # open a temporary output file ( you could also use the # -i "inplace edit feature", see perldoc perlrun ) # open(OUT,">file.$$") or die; # # print the contents of IN to OUT, # adding your additional line if we # see $dom as the first chars of the line # \Q and \E to quotemeta the domain since # it probably contains metachars like "." # while (<IN>) { print OUT; if (/^\Q$dom\E\s/) { print OUT "$user\@$dom\t$user\n"; } } # close both files and overwrite # IN with OUT via rename(). close IN; close OUT; rename("file.$$","file") or die;

    Update: Regarding the reply...other than the fact that it's opening a file called "file", the above is doing what you are asking. I think perhaps you are focused on the word seek. The seek() operator doesn't look for strings, it's given a byte offset and moves the file pointer.

    Also, you seem fixated on editing the file in place. This isn't really possible in your situation. Perl can make it appear that way with it's "-i" feature. See perlrun.

      my original code (before i changed the layout of the file) looked like this:
      my $datafile = '/etc/postfix/virtual'; my $req_addr = "$username\@$domain"; my ( $name, $atdomain ) = $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 ( $_ =~ /$atdomain/ && !$wrote_it ) { print FH "$req_addr\t$username\n" ; $wrote_it++ ; } } close FH ;
      Which would edit the /etc/postfix/virtual file if it looked like this:
      aaaa.com VIRTUALDOMAIN @aaaa.com postmaster bbbb.com VIRTUALDOMAIN @bbbb.com postmaster cccc.com VIRTUALDOMAIN @cccc.com postmaster

        I've seen that exact same code before (apart from the two lines that now have $atdomain instead of $domain), with the same question.

        I mumbled something about Tie::File. That might still be a nice solution.

        — Arien

      judging by what you have there, i don't think that i explained it correctly.
      I need the script to do the following:

      1. open /etc/postfix/virtual (which is arranged as above)
      2. seek to the line that begins with the same value as $dom
      3. do a return at the end of that line and print $user\@$dom\t$user

      Hope this is clearer. (i'm crap at explaining).
      Thanks
      so if $user is bob and $dom is aaaa.com, the file will look like below once it has been wrote to:
      aaaa.com VIRTUALDOMAIN bob@aaaa.com bob bbbb.com VIRTUALDOMAIN cccc.com VIRTUALDOMAIN dddd.net VIRTUALDOMAIN
      Thanks
Re: Basic Seek
by Aristotle (Chancellor) on Sep 08, 2002 at 17:40 UTC
    Editing in the middle of a file either requires creating a copy of the file with the new output and renaming back that over the old copy, or a relatively complex approach to copy the back of the file down X bytes after inserting your string of X bytes. For the first approach, something like this would do:
    my $infilename = "/etc/postfix/virtual"; open my $infile, "<", $infilename or die "Can't open $infilename to re +ad: $!\n"; open my $outfile, ">", "$infilename.new" or die "Can't open $infilenam +e.new to write: $!\n"; local $_; while(<$infile>) { print $outfile $_; print $outfile "$user\@$dom\t$user\n" if /^\Q$dom\E\b/; } close $infile; close $outfile; rename("$infilename.new", $infilename) or die "Couldn't replace $infil +ename with $infilename.new: $!\n";
    The simple way to use the other approach would be to use the Tie::File module.

    Makeshifts last the longest.

Re: Basic Seek
by rir (Vicar) on Sep 08, 2002 at 18:49 UTC
    You might be like me, I've acquired, from other days and
    languages, an aversion to reading in whole files.

    @mta = <MTA_FILE>;

    Twiddle your bits and rewrite your file.

    If it is your core, waste it not.
    If it is another's core, usurp it not.

    Slurp, slurp.