3

Is it possible to get console output from Python using Java? Here is an example of such output:

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

Now, the main goal is to get the above output by calling Python interpreter using Java. Here's my attempt:

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

I understand that calling the start method spawns a new process with its environment for execution. Writing python2 to a process's output stream results in creating another process. This is when the problem starts. I haven't been able to find a way of sending the command 2+2 to the Python interpreter (which is the child process of CMD) instead of its parent process.

To summarize: How do I run the Python interpreter, execute some commands inside it, and in the end, print the results to the standard output?

Luka
  • 1,718
  • 3
  • 19
  • 29
  • Your approach is not easy to implement, but it might be possible with multiple-threads. I think using [jython](http://jython.org/) would probably be easier. – Elliott Frisch Jul 30 '14 at 16:04
  • @ElliottFrisch: Could you explain in a few sentences why my code doesn't work as desired? – Luka Jul 30 '14 at 16:07
  • You haven't been able to find a way of sending the command 2+2 to the Python interpreter. Don't run `cmd`, just run `python2`. Add Threads. Or use jython. Or add a lot more detail here about why you expected to be able to control `cmd` to control subprocesses. Hint, I don't think you can. – Elliott Frisch Jul 30 '14 at 16:15
  • @ElliottFrisch: I tried running `python2` directly, but I didn't get any output. Sorry to bother you, but why do I need threads for this? Thanks much for your time. – Luka Jul 30 '14 at 16:22
  • Because you want it to work. I believe you can think of it like this, the process you are running is external to the JVM. So you need a thread to handle that process' output and another thread to provide input to that *external* process. – Elliott Frisch Jul 30 '14 at 16:25
  • @ElliottFrisch: How would you explain the fact that running `cmd` without running Python gives some output? Isn't `cmd` also external to the JVM? – Luka Jul 31 '14 at 09:09

2 Answers2

5

The Python executable can tell that you are running the command non-interactively. Once it realises it's being run non-interactively it will no longer attempt to interact with you; why bother printing to stdout or reading from stdin if noone is there?

To see that this is true you would attempt to run e.g. "ls" or "ps" and see that they work in your program, but then run e.g. "ftp" or "telnet" or "python" and see that don't work and output nothing.

In the parlance of Linux the problem is that the way we're running processes does not attach a TTY to them. The solution is to trick them into believing there's a TTY on the other end by creating a PTY.

Trick an application into thinking its stdin is interactive, not a pipe

On:

  • my Mac OS X 10.9.4 laptop with CPython 2.7.5 and Java 1.8.0_05 and
  • a Ubuntu 12.04.4 LTS server with CPython 2.7.5 and Java 1.7.0_55

the following works, albeit in a very ugly fashion:

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

I wouldn't do it like this. Instead I'd use threads to stay interactive and avoid blocking on reads and do what expect does, which is wait for certain prompts before writing out. In the code above I blindly sleep then hope for the best.

However I notice that you're using Windows, since you've run "cmd". I don't know how to create PTYs on Windows, sorry. I think though that you can get Expect for Windows; unbuffer is a utility within Expect:

http://expect.sourceforge.net/

Or try cygwin, but again I haven't tested this.

For further background on TTYs and PTYs see:

How to create a pseudo-tty for reading output and writing to input

Community
  • 1
  • 1
Asim Ihsan
  • 1,501
  • 8
  • 18
  • Thanks for the answer. Do you know where I can find the source code for `/usr/bin/script`? I'm really interested in how it works. Would it be possible to do something similar in Java for Windows? – Luka Jul 31 '14 at 16:18
  • @Luka I think an example for the source code for script can be found here: http://goo.gl/8YD1fP but I'm not sure. Take a look at the last link in my answer with respect to `forkpty()` (which doesn't exist on Windows). For Java maybe try "expect4j" (https://github.com/cverges/expect4j) or "expectit" (https://github.com/Alexey1Gavrilov/expectit), or if you want utilities not written in Java that you can call from Java search for "expect", "tcl expect windows", "pexpect". Good luck! – Asim Ihsan Jul 31 '14 at 16:45
  • Could you please modify your code to use threads like you mentioned in your answer? I can't get it to work with them. I always get blocked by the first reading statement (the program just hangs forever). Thanks. – Luka Aug 17 '14 at 06:34
  • @Luka I think it would be better to start a new question, post the code that you do have, and I'll take a look at it then. Since this question is already answered with an accepted answer it's better etiquette to start a new question. After you create a new question please post the URL here and I'll take another stab at a threaded solution. – Asim Ihsan Aug 18 '14 at 13:03
  • After a little bit more struggling and researching, I decided to create a new question right here: http://stackoverflow.com/questions/25472013/use-threads-to-talk-interactively-with-the-python-interpreter. Please take a look if you have time. Thanks in advance. – Luka Aug 24 '14 at 13:11
  • @Luka Thank you for creating the new question, it looks interesting and has some good feedback. I'll take a look over the next few days and update that question directly. – Asim Ihsan Aug 25 '14 at 14:41
  • You can force python to run interactively by providing the `-i` flag, like `python -i ....`. It wouldn't work in the more general case of the question to which you linked, but it should work for this case. – Brick Sep 04 '16 at 19:32
4

The answer from Asim Ihsan has the right root cause - python will know that it's not connected to an interactive terminal and not behave the way that you want. You can make python act in interactive mode, however, by passing the -i flag when you start python. This is simpler than what Asim proposes. You should just start python directly (no need the intermediary of starting cmd first). You'll use something like

ProcessBuilder pb = new ProcessBuilder('python', '-i');

and then proceed more or less as in the original question. Start the process, get the associated streams, and read and write to the streams.

Brick
  • 3,998
  • 8
  • 27
  • 47