tags:

views:

436

answers:

5

I need to run a string through a Java program and then retrieve the output. The Java program accepts the string through standard input. The following works:

my $output = `echo $string | java -jar java_program.jar`;

There is one problem: $string could be just about anything. Any thoughts on a good solution to this problem?

+3  A: 

If you can use CPAN modules (and I'm assuming most people can), look at Ivan's answer on using IPC::Run3. It should handle everything you need.

If you can't use modules, here's how to do things the plain vanilla way.

You can use a pipe to do your input, and it will avoid all those command line quoting issues:

open PIPE, "| java -jar java_program.jar";
print PIPE "$string";
close(PIPE);

It looks like you actually need the output of the command, though. You could open two pipes with something like IPC::Open2 (to and from the java process) but you risk putting yourself in deadlock trying to deal with both pipes at the same time.

You can avoid that by having java output to a file, then reading from that file:

open PIPE, "| java -jar java_program.jar > output.txt";
print PIPE "$string";
close(PIPE);

open OUTPUT, "output.txt";
while (my $line = <OUTPUT>) {
    # do something with $line
}
close(OUTPUT);

The other option is to do things the other way around. Put $string in a temporary file, then use it as input to java:

open INPUT, "input.txt";
print INPUT "$string";
close(INPUT); 

open OUTPUT, "java -jar java_program.jar < input.txt |";
while (my $line = <OUTPUT>) {
    # do something with the output
}
close(OUTPUT);

Note that this isn't the greatest way to do temporary files; I've just used output.txt and input.txt for simplicity. Look at the File::Temp docs for various cleaner ways to create temporary files more cleanly.

tgamblin
That looks great but how do I get the output of the command?
spudly
@spudly, you can't with that recommendation. You'd need to use open2(), check perldoc perlipc for "bidirectional communication"
jsoverson
Many uses of open2 or open3 on Windows systems are fatally hampered by the fact that you can only `select` against a socket on Win32. If you aren't on Windows or blocking is ok, then open2 or open3 may do the trick for you.
daotoad
A: 

The builtin IPC::Open2 module provides a function to handle bidirectional-piping without an external file.

spudly
+2  A: 

Have you looked into IPC::Run?

Syntax similar to this might be what you are looking for:

use IPC::Run qw( run );
my $input = $string;
my ($out, $err);
run ["java -jar java_program.jar"], \$input, \$out, \$err;
jsoverson
+6  A: 

I suggest you to look at IPC::Run3 module. It uses very simple interface and allow to get STDERR and STDOUT. Here is small example:

use IPC::Run3;
## store command output here
my ($cmd_out, $cmd_err);
my $cmd_input = "put your input string here";
run3([ 'java', '-jar', 'java_program.jar'], \$cmd_input, \$cmd_out, \$cmd_err);
print "command output [$cmd_out] error [$cmd_err]\n";

See IPC::Run3 comparation with other modules.

Ivan Nevostruev
+1  A: 

Create a pipeline just like your shell would.

Here's our scary string:

my $str = "foo * ~ bar \0 baz *";

We'll build our pipeline backwards, so first we gather the output from the Java program:

my $pid1 = open my $fh1, "-|";
die "$0: fork: $!" unless defined $pid1;

if ($pid1) {
  # grab output from Java program
  while (<$fh1>) {
    chomp;
    my @c = unpack "C*" => $_;
    print "$_\n  => @c\n";
  }
}

Note the special "-|" argument to Perl's open operator.

If you open a pipe on the command '-' , i.e., either '|-' or '-|' with 2-arguments (or 1-argument) form of open(), then there is an implicit fork done, and the return value of open is the pid of the child within the parent process, and 0 within the child process … The filehandle behaves normally for the parent, but i/o to that filehandle is piped from/to the STDOUT/STDIN of the child process.

The unpack is there to peek into the contents of the data read from the pipe.

In your program, you'll want to run the Java program, but the code below uses a reasonable facsimile:

else {
  my $pid2 = open my $fh2, "-|";
  die "$0: fork: $!" unless defined $pid2;

  if ($pid2) {
    $| = 1;
    open STDIN, "<&=" . fileno($fh2)
      or die "$0: dup: $!";

    # exec "java", "-jar", "java_program.jar";

    # simulate Java program
    exec "perl", "-pe", q(
      BEGIN { $" = "][" }
      my @a = split " ", scalar reverse $_;
      $_ = "[@a]\n";
    );
    die "$0: exec failed";
  }

Finally, the humble grandchild simply prints the scary string (which arrives on the standard input of the Java program) and exits. Setting $| to a true value flushes the currently selected filehandle and puts it in unbuffered mode.

  else {
    print $str;
    $| = 1;
    exit 0;
  }
}

Its output:

$ ./try
[*][zab][][rab][~][*][oof]
  => 91 42 93 91 122 97 98 93 91 0 93 91 114 97 98 93 91 126 93 91 42 93 91 111 111 102 93

Note that the NUL survives the trip.

Greg Bacon