Search code examples
pythonjavasubprocess

Supplying input in subprocess and print the inputs along with the outputs (Python running Java)


I want to execute this java program using python

import java.util.Scanner;
public class Input {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("Enter a number = ");
        int n = sc.nextInt();
        System.out.println("Number entered = "+n);
   }
}

I am taking a scanner input and do not want to take arguments using (String[] args).

I tried using

import subprocess
myInput = '1'
subprocess.run( [ 'java', 'Input.java' ], input=myInput.encode() )

for this python code, the output in terminal is

Enter a number = Number entered = 1

but the output I want is

Enter a number = 1
Number entered = 1

This question is similar to input to C++ executable python subprocess and Capturing INPUT and output with subprocess (Python running java) but there is no working solution. Is there an appropriate solution available now?


Solution

  • this answer is too old, there is now asyncio.subprocess, which can do asynchronous reads and writes to child processes but you need to know how asyncio and coroutines work.


    problem is there is no platform independent method to do it, so if you are on linux you can set the process stdout to non-blocking and safely read from it, but on windows this is not possible, so there are 3 workarounds.

    1. modify the java file to print "\n" after the question and receiver answer on different line, the python process can readline from stdout.
    2. know how many letters you want to read beforehand as if you are working with a socket.
    3. use threads

    this solution implements the second option, by writing both stdin and stdout to another buffer for temporary storage.

    import subprocess
    import io
    
    
    out_buff = io.StringIO()
    with subprocess.Popen(['java', 'Input.java'],
                          stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,
                          text=True) as proc:
    
        out_buff.write(proc.stdout.read(17))
        in_val = "1\n"
        out_buff.write(in_val)
        proc.stdin.write(in_val)
        proc.stdin.flush()  # required
        out_buff.write(proc.stdout.read())
    
    print(out_buff.getvalue())
    
    out_buff.close()
    
    Enter a number = 1
    Number entered = 1
    

    obviously if you are expecting a certain keyword before you answer, you can keep reading letter by letter from stdout until you know you have to input something, but if you wanted to read more than you actually can then your application will block. an option to avoid blocking is to use threads and this answer shows how to do so, but it gets complicated.

    Edit: to show the third option is possible using only 1 extra thread, but you have to put in delays

    import subprocess
    import io
    import threading
    import time
    
    def read_from_stdout(buffer,handle):
        try:
            while True:
                buffer.write(handle.read(1))
        except ValueError as e: # when reading from closed file
            pass
    
    out_buff = io.StringIO()
    with subprocess.Popen(['java', 'Input.java'],
                          stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,
                          text=True) as proc:
    
        thread = threading.Thread(target=read_from_stdout,args=(out_buff,proc.stdout))
        thread.start()
        time.sleep(0.1)  # wait for stdout to be read
        in_val = "1\n"
        out_buff.write(in_val)
        proc.stdin.write(in_val)
        proc.stdin.flush()  # required
        time.sleep(0.1)  # wait for stdout to be read
    
    print(out_buff.getvalue())
    
    out_buff.close()