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

I'm working on a qpsmtpd/Dovecot programming task to expand my knowledge. I want to deliver mail using Dovecot's LDA. The shell command that works looks like this:
/usr/lib/dovecot/deliver -f test@somedomain.com -d test@mailtask.dom < + demo.mail
Order is important. The email file goes on the end. Question 1: What are other more convoluted ways of writing that command? I tried to pipe and it just didn't work. It should be obvious my STDIN-foo is weak. So... very... weak... My test code that doesn't work looks like this:
use strict; open FH, "< ./demo.mail"; my @all = <FH>; close FH; my $stringified = ''; foreach (@all) { $stringified .= $_; } #print $stringified; close STDOUT; my $test = open (STDOUT, ' $stringified | /usr/lib/dovecot/deliver -f +test\@somedomain.com -d test\@mailtask.dom '); print $test;
Question 2: how do I write the open line to work like the command line version?

Replies are listed 'Best First'.
Re: Newbie: Pipe/STDIN Clarification
by mrstlee (Beadle) on Nov 05, 2011 at 11:41 UTC
    You don't need to touch STDOUT actually, and you can simplify the file read:
    my $stringified = ''; { #Go into local slurp mode .. local $/=undef; open FH, "< ./demo.mail"; $stringified = <FH>; close FH; open (CMD_OUT, "/usr/lib/dovecot/deliver -f +test\@somedomain.com -d test\@mailtask.dom $stringified"|); my $test = <CMD_OUT>; print $test; close CMD_OUT; }


    The 'local $/=undef' bit turns off the normal record separator (newline by default). So when the filehandle is read the data is slurped in as one record. The point of enclosing in {}'s is to localise the effect - outside the {}'s the default separator will be used.

    You could also use the back-tick notation to run the shell command:
    my $test = `/usr/lib/dovecot/deliver -f +test\@somedomain.com -d test\@mailtask.dom $stringified`;
    There are other ways of course. This is perl after all.
      Two problems: (1) your "local slurp" isn't local enough, and (2) I think you misread the OP's intention -- mpapet wants to write to the "deliver" process, not read from it. (And BTW, you should indent, and be careful about how line-wrapping in code blocks is handled here at PM.)
      my $stringified; { local $/; # defaults to undef open my $fh, '<', './demo.mail'; # 3-arg open is nice $stringified = <$fh>; } # input's done, end-of-block closes the file and reverts to normal i/o my @cmd = qw( /usr/lib/dovecot/deliver -f test@somedomain.com -d test@mailtask.dom ); open( CMD_OUT, '|-', @cmd ) or die $!; print CMD_OUT $stringified; close CMD_OUT;
      (Updated to use lexically-scoped file handle in the localized input block -- that's what will cause the file to be closed on leaving the block.)
        Ah - but as you say I misread the OP's intention. The localised slurp also encompassed the (incorrectly) assumed read from the command - which I assumed would come with line feeds.

        Thanks for the tip on indents.
      Thanks for both replies.

      If I try to pass the contents of the variable, it errors out. "Fatal: Unknown argument: From" That's the first line of my demo.mail file. I think it's safe to assume it is reading the string as a file name only.

      That means one more step of writing the email out to disk using the lda program as-is.

        It's unfortunate that the first two replies to your OP (from Anonymous Monk and mrstlee) were wrong, and/or didn't quite get your question. Of course there's no guarantee that my replies are correct, either...
Re: Newbie: Pipe/STDIN Clarification
by Anonymous Monk on Nov 05, 2011 at 05:16 UTC
    See perlintro/perlopentut, basically
    open my($out), 'command |' or die $!; print $out $_ while <$in>; close $out;

    Using system can be ok :) system "command < file";

      That open statement has things the wrong way around. To open a file handle for printing to a pipeline command, it goes like this:
      open my $fh, '| command' or die $!;
      Or better yet, use the 3-arg open call -- and while we're at it, let's provide the command as an array, and get the process-ID:
      my @command = qw/downstream_process -a option1 -b option2 -etc/; my pid = open( my $fh, '|-', @command ) or die $!;
      (The form of open call shown by AnonyMonk would be for running a command so that you can read its output, and you wouldn't write to that file handle.)
Re: Newbie: Pipe/STDIN Clarification
by mrstlee (Beadle) on Nov 05, 2011 at 13:31 UTC
    And while I think of it there are neater ways to join arrays of strings:
    #Works for all perl versions join '', @string_arr; #Obviously you can use any string as the first a +rg of join e.g. "\n" #Since 5.10(I think?Maybe 5.8) open STRING_HNDL, ">", \(my $str); print STRING_HNDL @string_arr; close STRING_HNDL; #$str now contains the strings in @string_arr concatenated together print "@string_arr"; # A space is put in between successive elements o +f @string_arr
    Have fun!