You are on the right track with the code. There are only some minor things you missed.
Let's start with your read method:
private void read(String command){ [...] // Write to Process if (outStream != null) { [...] try { writer.write(command + "\n"); // add newline so your input will get proceed writer.flush(); // flush your input to your process } catch (IOException e1) { e1.printStackTrace(); } } // ELSE!! - if no outputstream is available // Execute Command else { try { exec(command); } catch (IOException e) { // Handle the exception here. Mostly this means // that the command could not get executed // because command was not found. println("Command not found: " + command); } } inPane.setText(""); }
Now fix your exec method. You should use separate threads to read the normal output process and output errors. In addition, I present a third thread that waits for the process to complete and closes the output stream, so the next user input is not intended for the process, but is a new command.
private void exec(String command) throws IOException{ Process pro = Runtime.getRuntime().exec(command, null); inStream = pro.getInputStream(); inErrStream = pro.getErrorStream(); outStream = pro.getOutputStream(); // Thread that reads process output Thread outStreamReader = new Thread(new Runnable() { public void run() { try { String line = null; BufferedReader in = new BufferedReader(new InputStreamReader(inStream)); while ((line = in.readLine()) != null) { println(line); } } catch (Exception e) { e.printStackTrace(); } System.out.println("Exit reading process output"); } }); outStreamReader.start(); // Thread that reads process error output Thread errStreamReader = new Thread(new Runnable() { public void run() { try { String line = null; BufferedReader inErr = new BufferedReader(new InputStreamReader(inErrStream)); while ((line = inErr.readLine()) != null) { println(line); } } catch (Exception e) { e.printStackTrace(); } System.out.println("Exit reading error stream"); } }); errStreamReader.start(); // Thread that waits for process to end Thread exitWaiter = new Thread(new Runnable() { public void run() { try { int retValue = pro.waitFor(); println("Command exit with return value " + retValue); // close outStream outStream.close(); outStream = null; } catch (InterruptedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }); exitWaiter.start(); }
Now this should work.
If you enter ipconfig , it prints the output of the command, closes the output stream, and is ready for a new command.
If you enter cmd , it prints the output and allows you to enter additional cmd commands, such as dir or cd , and so on, until you type exit . Then it closes the output stream and is ready for a new team.
You may have problems running python scripts because there are problems reading Process InputStreams with Java if they are not dumped to the system pipeline.
See python script example
print "Input something!" str = raw_input() print "Received input is : ", str
You can run this with your Java program and also enter input, but you will not see the output of the script until the script completes.
The only fix I could find was to manually clear the output in the script.
import sys print "Input something!" sys.stdout.flush() str = raw_input() print "Received input is : ", str sys.stdout.flush()
Running this script will crash as you expect.
You can learn more about this issue at
- Java: is there a way to run a system command and print the output at run time?
- Why reading from "InputStream block altough" process data is available
- Java: it is not possible to get stdout data from a process unless manually clearing it
EDIT: I found another very simple solution to the problem with stdout.flush() using Python scripts. Run them using python -u script.py and you do not need to crash manually. This should solve your problem.
EDIT2: We discussed in the comments that Stream will mix up with this solution and error, as they work in different streams. The problem here is that we cannot distinguish whether the recording of the output ends when a stream of the error stream appears. Otherwise, classic lock-based thread planning can handle this situation. But we have a continuous stream until the process is complete regardless of whether the data streams or not. Therefore, we need a mechanism that records how much time has passed since the last line was read from each stream.
To do this, I will introduce a class that receives an InputStream and runs Thread to read incoming data. This thread stores each line in the queue and stops when the end of the thread arrives. In addition, it contains the time when the last line was read and added to the queue.
public class InputStreamLineBuffer{ private InputStream inputStream; private ConcurrentLinkedQueue<String> lines; private long lastTimeModified; private Thread inputCatcher; private boolean isAlive; public InputStreamLineBuffer(InputStream is){ inputStream = is; lines = new ConcurrentLinkedQueue<String>(); lastTimeModified = System.currentTimeMillis(); isAlive = false; inputCatcher = new Thread(new Runnable(){ @Override public void run() { StringBuilder sb = new StringBuilder(100); int b; try{ while ((b = inputStream.read()) != -1){
With this class, we could combine the output stream and the error stream into one. It lives while the data input buffer streams live and have no data. In each run, he checks to see if some time has passed since the last exit was read, and if he prints all the unsealed lines with a stroke. Same thing with error output. Then he sleeps for some milliseconds, so as not to waste CPU time.
private void exec(String command) throws IOException{ Process pro = Runtime.getRuntime().exec(command, null); inStream = pro.getInputStream(); inErrStream = pro.getErrorStream(); outStream = pro.getOutputStream(); InputStreamLineBuffer outBuff = new InputStreamLineBuffer(inStream); InputStreamLineBuffer errBuff = new InputStreamLineBuffer(inErrStream); Thread streamReader = new Thread(new Runnable() { public void run() { // start the input reader buffer threads outBuff.start(); errBuff.start(); // while an input reader buffer thread is alive // or there are unconsumed data left while(outBuff.isAlive() || outBuff.hasNext() || errBuff.isAlive() || errBuff.hasNext()){ // get the normal output if at least 50 millis have passed if(outBuff.timeElapsed() > 50) while(outBuff.hasNext()) println(outBuff.getNext()); // get the error output if at least 50 millis have passed if(errBuff.timeElapsed() > 50) while(errBuff.hasNext()) println(errBuff.getNext()); // sleep a bit bofore next run try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Finish reading error and output stream"); } }); streamReader.start(); // remove outStreamReader and errStreamReader Thread [...] }
This may not be the perfect solution, but it should handle the situation here.
EDIT (8/31/2016)
We discussed in the comments that there is still a problem with the code when implementing the stop button, which kills the initial one using Process#destroy() . A process that produces a very large output, for example. in an infinite loop, destroy immediately by calling destroy() . But since it has already produced a lot of products that should be consumed by our streamReader we cannot return to the normal behavior of the program.
Therefore, we need small changes here:
We will introduce the destroy() method for InputStreamLineBuffer , which stops the output and clears the queue.
The changes will look like this:
public class InputStreamLineBuffer{ private boolean emergencyBrake = false; [...] public InputStreamLineBuffer(InputStream is){ [...] while ((b = inputStream.read()) != -1 && !emergencyBrake){ [...] } } [...]
And some small changes in the main program
public class ExeConsole extends JFrame{ [...]
Now he should be able to destroy even some spamming processes.
Note. I found out that Process#destroy() cannot destroy child processes. So, if you run cmd in windows and run the Java program from there, you will end the destruction of the cmd process while the Java program is still running. You will see it in the task manager. This problem cannot be solved with java. it will require some os depend on external tools to get pids of these processes and kill them manually.