Search code examples
perlforkstdin

Spawn a program from Perl and communicate with it via STDIN/STDOUT


I have a C program that simply runs a loop that reads a JSON structure from STDIN and writes a line to STDOUT.

In order to support a variety of front-end formats, I want to write a Perl program that repeatedly reads data, converts it to JSON, submits it to the C program, and receives the output -- as if I were using qx// to invoke the C program, only without launching it afresh each time.

This thread describes the same problem, except the parent process is in C. I wondered whether Perl provided a way to do this more easily. It would be preferable (but not essential) for the C program to stay the same and be unaware whether it was forked by Perl or run from the command line, if possible.

To illustrate (note - using Perl for the child, but hopefully the same principles apply):

File parent.pl

#!/usr/bin/env perl 
use warnings;
use strict;
$|++;

# {{ spawn child.pl }}
while (1) {
    print "Enter text to send to the child: ";
    my $text = <>;
    last if !defined $text;
    # {{ send $text on some file descriptor to child.pl }}
    # {{ receive $reply on some file descriptor from child.pl }}
}

File child.pl:

#!/usr/bin/env perl 
use warnings;
use strict;
$|++;
while (my $line = <STDIN>) {
    chomp $line;
    $line .= ", back atcha.\n";
    print $line;
}

Execution:

$ parent.pl
Enter text to send to the child: hello
hello, back atcha.
Enter text to send to the child: 

UPDATE:

The caveats for using open2, stated both by @ikegami below and in Programming Perl / Interprocess Communication, don't seem to me to apply here, given:

  • I don't care about STDERR (which would require open3 and select)
  • I control the child source code and can therefore guarantee that autoflushing occurs.
  • The protocol is strictly send one line, receive one line.

Solution

  • Given these conditions from the original question ...

    • You don't care about reading STDERR
    • You control the child source code and can therefore guarantee that autoflushing occurs.
    • The protocol is strictly send one line, receive one line.

    ... the following will work. (Note that the child is written here in Perl but could also be C.)

    parent.pl

    #!/usr/bin/env perl 
    use warnings;
    use strict;
    use IPC::Open2;
    $|=1;
    my $pid = open2(my $ifh, my $ofh, 'child.pl') or die;
    while (1) {
        print STDOUT "Enter text to send to the child: ";
        my $message = <STDIN>;
        last if !defined $message;
        print $ofh $message;   # comes with \n
        my $reply = <$ifh>;
        print STDOUT $reply;
    }
    close $ifh or die;
    close $ofh or die;
    waitpid $pid, 0;
    

    child.pl

    #!/usr/bin/env perl 
    use warnings;
    use strict;
    $|=1;
    
    while (my $line = <STDIN>) {
        chomp $line;
        print STDOUT $line . ", back atcha.\n";
    }