我该如何从Perl向Java命令输入数据流?

6

我需要将一个字符串通过Java程序运行,并获取输出。Java程序通过标准输入接受字符串。以下是有效的代码:

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

有一个问题: $string 可能是任何东西。对于这个问题,您有什么好的解决方案吗?

5个回答

6
如果您能够使用CPAN模块(我假设大多数人都可以),请查看Ivan's answer,了解如何使用IPC::Run3。它应该可以处理您需要的所有内容。
如果您无法使用模块,则可以使用纯文本方式执行以下操作。
您可以使用管道来进行输入,这将避免所有那些命令行引用问题:
open PIPE, "| java -jar java_program.jar";
print PIPE "$string";
close(PIPE);

看起来你实际上需要命令的输出。你可以使用IPC::Open2打开两个管道(一个用于进程输入,一个用于输出),但这样做会冒着同时处理两个管道而导致死锁的风险。

你可以通过让Java将输出写入文件,然后从该文件中读取来避免这种情况:

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);

另一种选择是反过来做。将$string放入临时文件中,然后将其作为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);

请注意,这不是处理临时文件的最佳方法;我只是为了简单起见使用了output.txtinput.txt。请查看File::Temp文档以获取更多创建临时文件的更干净的方法。

1
@spudly,你不能只凭那个建议就解决问题。你需要使用open2(),可以查看perldoc perlipc中的“双向通信”。 - jsoverson
在Windows系统上,许多使用open2或open3的操作受到致命的阻碍,因为你只能针对Win32上的套接字执行select操作。如果你不在Windows上,或者阻塞是可以接受的,那么open2或open3可能适合你。 - daotoad

6

我建议您查看IPC::Run3模块。它使用非常简单的接口,并允许获取STDERRSTDOUT。这里是一个小例子:

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";

请参考 IPC::Run3 与其他模块的比较


2

创建一个与您的Shell类似的流水线。

这是我们可怕的字符串:

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

我们将反向构建我们的流水线,因此首先我们收集Java程序的输出:
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";
  }
}

请注意Perl的open操作符中特殊的"-|"参数。

如果你在命令'-'上打开一个管道,即使用open()的2个参数(或1个参数)形式的'|-''-|',则会隐式地进行fork,并且open的返回值是父进程中子进程的pid,而在子进程中是0...文件句柄对于父进程表现正常,但对于该文件句柄的i/o从/到子进程的STDOUT/STDIN进行传输。

unpack用于查看从管道读取的数据内容。
在您的程序中,您需要运行Java程序,但下面的代码使用了一个合理的类似物:
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";
  }

最后,谦虚的孙子只需打印恐怖字符串(该字符串在Java程序的标准输入上到达)并退出。将$|设置为真值会刷新当前选定的文件句柄并将其置于无缓冲模式。
  else {
    print $str;
    $| = 1;
    exit 0;
  }
}

输出如下:

$ ./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

请注意,NUL字符得以保留。


2

你是否研究过IPC::Run?这可能是你正在寻找的语法:

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

1

内置的IPC::Open2模块提供了一个函数来处理双向管道,而无需外部文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接