如何在Java中运行Python解释器并获取输出?
可以用Java来获取Python的控制台输出吗?下面是一个这样的输出示例:
Python 3.3.4 (v3.3.4:7ff62415e426, Feb 10 2014, 18:13:51) [MSC v.1600 64 bit (AMD64)]
on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> 2+2
4
>>>
现在,主要目标是通过Java调用Python解释器来获得上面的输出。以下是我的尝试:
//...
//Irrelevant code omitted
ProcessBuilder processBuilder = new ProcessBuilder("cmd");
processBuilder.redirectErrorStream(true);
processBuilder.start();
processBuilder.command("python2");
Process pythonProcess = processBuilder.start();
OutputStream outputStream = pythonProcess.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(outputStream);
osw.write("2+2\r\nquit()\r\n");
osw.flush();
osw.close();
InputStream inputStream = pythonProcess.getInputStream();
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(inputStream));
String line;
while( (line=bufferedReader.readLine())!=null) {
System.out.println(line);
}
//...
//Irrelevant code omitted
我明白调用start
方法会启动一个新的进程,并为其设置执行环境。往一个进程的输出流写入python2
会导致创建另一个进程。这时候问题就来了。我一直没找到办法把命令2+2
发送给Python解释器(它是CMD的子进程),而不是发送给它的父进程。
总结一下:我该如何运行Python解释器,在里面执行一些命令,最后把结果打印到标准输出上呢?
2 个回答
Asim Ihsan的回答指出了问题的根本原因——Python会知道它没有连接到一个交互式终端,因此不会按照你想要的方式运行。不过,你可以通过在启动Python时加上-i
这个参数,让Python进入交互模式。这样做比Asim提议的方法要简单。你只需要直接启动Python(不需要先打开cmd
)。你可以使用类似下面的命令:
ProcessBuilder pb = new ProcessBuilder('python', '-i');
然后接下来就可以按照原问题中的方式进行操作。启动进程,获取相关的流,然后对这些流进行读写。
Python的执行程序能够判断你是在非交互模式下运行命令。一旦它意识到自己是在这种模式下,它就不会再尝试和你互动了;如果没有人可以回应,打印信息到标准输出或从标准输入读取又有什么意义呢?
要验证这一点,你可以尝试运行像“ls”或“ps”这样的命令,看看它们在你的程序中能正常工作,但如果你运行“ftp”或“telnet”或“python”,就会发现它们不工作,而且没有任何输出。
在Linux的术语中,问题在于我们运行进程的方式没有给它们连接一个TTY。解决这个问题的方法是通过创建一个PTY来欺骗它们,让它们相信另一端有一个TTY。
欺骗一个应用程序,让它认为它的标准输入是交互式的,而不是管道
在:
- 我的Mac OS X 10.9.4笔记本上,使用的是CPython 2.7.5和Java 1.8.0_05,以及
- 一台运行Ubuntu 12.04.4 LTS的服务器,使用的是CPython 2.7.5和Java 1.7.0_55
以下代码可以工作,尽管方式很丑陋:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
class Foo {
public static void main(String[] args) throws IOException, InterruptedException {
// https://stackoverflow.com/questions/1401002/trick-an-application-into-thinking-its-stdin-is-interactive-not-a-pipe
//
// Using script or unbuffer is the important catch. Without this
// step you cannot use stdin in Python interactively, even with
// python -u. At least script comes with Linux/Mac OS X, but
// unbuffer works fine too.
ProcessBuilder pb;
switch(System.getProperty("os.name")) {
case "Mac OS X":
pb = new ProcessBuilder(
"/usr/bin/script", "-q", "/dev/null", "/usr/bin/python");
break;
default:
// Linux
pb = new ProcessBuilder(
"/usr/bin/script", "-qfc", "/usr/bin/python", "/dev/null");
}
// This doesn't make a difference.
// pb.redirectErrorStream(true);
Process p = pb.start();
char[] readBuffer = new char[1000];
InputStreamReader isr = new InputStreamReader(p.getInputStream());
BufferedReader br = new BufferedReader(isr);
int charCount;
boolean written = false;
while(true) {
if (!br.ready() && !written) {
// Ugly. Should be reading for '>>>' prompt then writing.
Thread.sleep(1000);
if (!written) {
written = true;
OutputStream os = p.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os);
BufferedWriter bw = new BufferedWriter(osw);
bw.write("2+2");
bw.newLine();
bw.write("quit()");
bw.newLine();
bw.flush();
bw.close();
}
continue;
}
charCount = br.read(readBuffer);
if (charCount > 0)
System.out.print(new String(readBuffer, 0, charCount));
else
break;
}
}
}
我不会这样做。相反,我会使用线程来保持交互性,避免在读取时阻塞,并像expect那样,在写出之前等待特定的提示。在上面的代码中,我盲目地让程序休眠,然后希望一切顺利。
不过,我注意到你在使用Windows,因为你运行了“cmd”。我不知道如何在Windows上创建PTY,抱歉。不过我想你可以在Windows上获取Expect;unbuffer是Expect中的一个工具:
http://expect.sourceforge.net/
或者试试cygwin,但我也没有测试过这个。
关于TTY和PTY的更多背景知识可以参考: