I would like to print live output of a process in java; but the output is (almost) only available when the process stops execution.
In other words, when I run the same process with the command line, I get more frequent feedback (output) than when I execute it with Java.
I tested the following code to print the output every 250ms:
private static String printStream(BufferedReader bufferedReader) {
try {
String line = bufferedReader.readLine();
if(line != null) {
System.out.println(line);
}
return line;
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
}
public static void sendProcessStreams(Process process, SendMessage sendMessage) {
String[] command = { "/bin/bash", "-c", "custom_process"};
ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.directory(new File("."));
Process process = processBuilder.start();
BufferedReader inputBufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()), 1);
BufferedReader errorBufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream()), 1);
System.out.println("Start reading process");
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
String inputLine = printStream(inputBufferedReader);
String errorLine = printStream(errorBufferedReader);
if(inputLine == null && errorLine == null && !process.isAlive()) {
timer.cancel();
}
}
}, 0, 250);
}
Yet it only prints two lines, and then waits for the process to end before printing everything else.
Is it possible to get more frequent feedback from an external process? If not, Why not?
Your code basically (as far as reading from the output) works correctly, the problem is surely for another reason, please build a minimal problem that is reproducible (eg. what is custom_process, why you use Timer when you can use the current thread, ...).
Anyway, here's an example reading in realtime the output:
final Process proc = new ProcessBuilder(
"/bin/bash", "-c",
"for i in `seq 1 10`; do echo $i; sleep $((i % 2)); done")
.start();
try(InputStreamReader isr = new InputStreamReader(proc.getInputStream())) {
int c;
while((c = isr.read()) >= 0) {
System.out.print((char) c);
System.out.flush();
}
}
With output:
1 (and wait one second)
2
3 (and wait one second)
4
5 (and wait one second)
6
7 (and wait one second)
8
9 (and wait one second)
10
it seems like you are dealing with multithreading-problems and not with getting the output of the process.
I just made this demo class that you can use:
CommandExecTest.java
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
public class CommandExecTest {
public static void main(String[] args) throws InterruptedException {
String executable = "cmd";
String[] commandParams = {"#ping -n 5 localhost","echo \"hello world\"","exit 123"};
boolean passCommandsAsLinesToShellExecutableAfterStartup = true;
AsyncExecutor asyncExecutor = new AsyncExecutor(executable, commandParams,passCommandsAsLinesToShellExecutableAfterStartup);
System.out.println("x"+"/x\tsecs in main thread \t\t status:"+asyncExecutor.runstate+" of async thread that monitors the process");
asyncExecutor.start();//start() invokes the run() method as a detached thread
for(int i=0;i<10;i++) {
// you can do whatever here and the other process is still running and printing its output inside detached thread
Thread.sleep(1000);
System.out.println(i+"/10\tsecs in main thread \t\t status:"+asyncExecutor.runstate+" of async thread that monitors the process");
}
asyncExecutor.join(); // main thread has nothing to do anymore, wait till other thread that monitor other process finishes as well
System.out.println("END OWN-PROGRAMM: 0 , END OTHER PROCESS:"+asyncExecutor.processExitcode);
System.exit(0);
}
}
Runstate.java
public static enum Runstate {
CREATED, RUNNING, STOPPED
}
AsyncExecutor.java
public static class AsyncExecutor extends Thread{
private String executable;
private String[] commandParams;
public ArrayList<String> linesSoFarStdout = new ArrayList<>();
public ArrayList<String> linesSoFarStderr = new ArrayList<>();
public Runstate runstate;
public int processExitcode=-1;
private boolean passCommandsAsLinesToShellExecutableAfterStartup = false;
public AsyncExecutor(String executable, String[] commandParams) {
this.executable=executable;
this.commandParams=commandParams;
this.runstate=Runstate.CREATED;
this.passCommandsAsLinesToShellExecutableAfterStartup=false;
}
/**
* if you want to run a single-process with arguments use <b>false</b> example executable="java" commandParams={"-jar","myjarfile.jar","arg0","arg1"}
* <p>
* if you want to run a shell-process and enter commands afterwards use <b>true</b> example executable="cmd" commandParams={"#ping -n 5 localhost","echo \"hello world\"","exit 123"}
* #param executable
* #param commandParams
* #param passCommandsAsLinesToShellExecutableAfterStartup
*/
public AsyncExecutor(String executable, String[] commandParams, boolean passCommandsAsLinesToShellExecutableAfterStartup) {
this.executable=executable;
this.commandParams=commandParams;
this.runstate=Runstate.CREATED;
this.passCommandsAsLinesToShellExecutableAfterStartup=passCommandsAsLinesToShellExecutableAfterStartup;
}
#Override
public void run() {
this.runstate=Runstate.RUNNING;
// 1 start the process
Process p = null;
try {
if(passCommandsAsLinesToShellExecutableAfterStartup) {
// open a shell-like process like cmd and pass the arguments/command after opening it
// * example:
// * open 'cmd' (shell)
// * write 'echo "hello world"' and press enter
p = Runtime.getRuntime().exec(new String[] {executable});
PrintWriter stdin = new PrintWriter( p.getOutputStream());
for( int i = 0; i < commandParams.length; i++) {
String commandstring = commandParams[i];
stdin.println( commandstring);
}
stdin.close();
}
else {
// pass the arguments directly during startup to the process
// * example:
// * run 'java -jar myexecutable.jar arg0 arg1 ...'
String[] execWithArgs = new String[commandParams.length+1];
execWithArgs[0] = executable;
for(int i=1;i<=commandParams.length;i++) {
execWithArgs[i]=commandParams[i-1];
}
p = Runtime.getRuntime().exec( execWithArgs);
}
// 2 print the output
InputStream is = p.getInputStream();
BufferedReader br = new BufferedReader( new InputStreamReader( is));
InputStream eis = p.getErrorStream();
BufferedReader ebr = new BufferedReader( new InputStreamReader( eis));
String lineStdout=null;
String lineStderr=null;
while(p.isAlive()) {
Thread.yield(); // *
// * free cpu clock for other tasks on your PC! maybe even add thread.sleep(milliseconds) to free some more
// * everytime this thread gets cpu clock it will try the following codeblock inside the while and yield afterwards for the next time it gets cpu-time from sheduler
while( (lineStdout = br.readLine()) != null || (lineStderr = ebr.readLine()) != null) {
if(lineStdout!=null) {
System.out.println(lineStdout);
linesSoFarStdout.add(lineStdout);
}
if(lineStderr!=null) {
System.out.println(lineStderr);
linesSoFarStderr.add(lineStderr);
}
}
}
// 3 when process ends
this.processExitcode = p.exitValue();
}
catch(Exception e) {
System.err.println("Something went wrong!");
e.printStackTrace();
}
if(processExitcode!=0) {
System.err.println("The other process stopped with unexpected existcode: " + processExitcode);
}
this.runstate=Runstate.STOPPED;
}
}
Related
Need to run sqlcmd to control local-connections to the database.
When i try to read the OutputStream to get the sql "prompt", the read() method simply never returns.
tried running the process with ProcessBuilder and Runtime.getRuntime().exec()
also tried running under "cmd /c"
when running sqlcmd from the command line, i get a prompt like "1>". expected to see that in the OutputStream ...
this is my code - i simply call start with "sqlcmd"
code example:
package org.example;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws Exception {
System.out.println(start("sqlcmd"));
System.out.println(send("select * from spt_monitor\ngo\n"));
}
public static Process process = null;
public static String start(String command) throws Exception {
if(process != null && process.isAlive()) {
throw new Exception("another session already exists");
} else {
ProcessBuilder builder = new ProcessBuilder(command);
builder.redirectErrorStream(true);
process = builder.start();
return "";//response();
}
}
public static String send(String command) throws Exception {
if(process == null || !process.isAlive()) {
throw new Exception("no local session exists");
} else {
process.getOutputStream().write(command.getBytes());
process.getOutputStream().flush();
return response();
}
}
private static String response() throws IOException {
StringBuilder sb = new StringBuilder();
char[] array = new char[1024];
System.out.println("reading output...");
try (InputStreamReader isr = new InputStreamReader(process.getInputStream())) {
int len;
do {
len = isr.read(array);
if(len > 0)
sb.append(String.valueOf(array));
} while (len > 0);
}
return sb.toString();
}
}
This hopefully will give a clearer explanation as to why your code does not finish as you expect. You have started sub-process sqlcmd, and sqlcmd outputs a prompt, receives your command(s), processes each command and outputs another prompt.
The sqlcmd process is waiting for more commands but you have not sent more after send(). At this point though your response() call is stuck in a blocking loop trying to read the character AFTER the last prompt. This means that response() will never exit normally - even though it has processed some of the commands and appended to a StringBuilder.
To see this more clearly, fix a small bug in response() with incorrect size of the array in String.valueOf() and add a System.out to tell you what has been read so far by response():
private static String response() throws IOException {
StringBuilder sb = new StringBuilder();
char[] array = new char[1024];
System.out.println("reading output...");
try (InputStreamReader isr = new InputStreamReader(process.getInputStream())) {
int len;
do {
len = isr.read(array);
if(len > 0) {
String r = String.valueOf(array, 0, len);
System.out.println("response() has read a value: "+r);
sb.append(r);
}
} while (len > 0);
}
return sb.toString();
}
Re-run and you should see that isr.read(array) HAS worked and should print out the results of select * from spt_monitor but will block on next or following isr.read(array) call when the prompt has been printed - and response() never exits because no more commands are received to process.
There is no more STDOUT to process unless you send sqlcmd an exit command OR terminate the STDIN which causes the sqlcmd process to end - both of which would mean that response() could exit normally.
You should be able to make response() work by terminating the STDIN by adding process.getOutputStream().close() at the end of send() or after calling send(). However that means that sqlcmd will exit.
I have written a code which will give me all the processes which are currently running on Windows and their instances count. I have also written a code which will kill a process by name specified. But I want to kill the process which has the most uptime, for example if I have 5 instances running for notepad then I want to kill only one instance which has the highest uptime. Below is the code which I have written. Thanks in advance.
public static void method3() {
List<String>processes=new ArrayList<>();
Map<String,Integer>mp=new LinkedHashMap<>();
try {
String line;
Process process=Runtime.getRuntime().exec("tasklist.exe /fo csv /nh");
BufferedReader in=new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((line = in.readLine()) != null) {
if (!line.trim().equals("")) {
line = line.substring(0,line.indexOf(","));
line=line.replaceAll("^\"|\"$", "");
processes.add(line);
}
}
System.out.println();
//Putting process name and it's instances count into a map.
for(int i=0;i<processes.size();i++) {
int count=Collections.frequency(processes,processes.get(i));
mp.put(processes.get(i),count);
}
//getting specific value count and killing a processes if it is active based on name.
Set<String>key=mp.keySet();
for(String k:key) {
if(k.equalsIgnoreCase("notepad.exe")) {
Runtime.getRuntime().exec("taskkill /F /IM "+k);
}
}
for(Entry<String, Integer> ent:mp.entrySet()) {
System.out.println(ent.getKey()+"="+ent.getValue());
}
in.close();
}catch(Exception e) {
e.printStackTrace();
}
}
If you are using at least JDK 9, you can use the Process API that was added since that version. Method allProcesses returns a stream of all the current processes.
You filter for the processes containing the relevant name (in your question you use notepad.exe) and sort them by totalCpuDuration and collect them to a list where the last element in the list is the process with the highest uptime, i.e. the longest duration.
As far as I am aware, the Process API does not have a method for killing a process, hence I used class ProcessBuilder for that.
import java.io.IOException;
import java.time.Duration;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class TaskList {
public static void main(String[] args) {
String s = "notepad.exe";
List<ProcessHandle> list = ProcessHandle.allProcesses() // returns 'Stream<ProcessHandle>'
.filter(h -> h.info()
.command() // returns 'Optional<String>'
.orElse("") // returns path to executable file or empty string
.contains(s))
.sorted(Comparator.comparing(h -> h.info()
.totalCpuDuration() // returns 'Optional<Duration>'
.orElse(Duration.ZERO))) // returns actual duration or a "zero" duration
.collect(Collectors.toList());
int count = list.size();
if (count > 0) {
long pid = list.get(count - 1)
.pid();
ProcessBuilder pb = new ProcessBuilder("taskkill", "/PID", Long.toString(pid));
pb.inheritIO();
try {
Process p = pb.start();
int status = p.waitFor();
System.out.println("status = " + status);
}
catch (InterruptedException | IOException x) {
x.printStackTrace();
}
}
else {
System.out.printf("No '%s' process found.%n", s);
}
}
}
I am using Java ProcessStream to write input commands to powershell on my local machine. Now, the problem is that along with the output, I am seeing input commands as well. How can I restrict input command from being shown on the output.
Below is the code to reproduce the same:
public class Example {
public static boolean isAlive(Process o) {
try {
p.exitValue();
return false;
} catch (Exception e) {return true;}
}
public stati void main(String[] args) throws IOException {
ProcessBuilder builder = new ProcessBuilder("powershell");
builder.redirectErrorStream(true);
Process process = builder.start();
InputStream out = process.getInputStream();
OutputStream in = process.getOutputStream();
String bufferString = "echo hello \n";
byte[] buffer = bufferString.getBytes();
int n = buffer.length;
in.write(buffer, 0, n);
in.flush();
while(isAlive(process)) {
int no = out.available();
if (no >0) {
int m = out.read(buffer, 0, Math.min(no, buffer.length));
System.out.println(new String(buffer, 0, m));
}
}
}
}
OUTPUT:
PS C://> echo hello
hello
I need only "hello" in output.
The following works for me (but the java program never terminates and I have to kill it).
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
public class Example {
public static boolean isAlive(Process p) {
try {
p.exitValue();
return false;
}
catch (Exception e) {
return true;
}
}
public static void main(String[] args) throws IOException {
ProcessBuilder builder = new ProcessBuilder("powershell", "-NoLogo");
builder.redirectErrorStream(true);
Process process = builder.start();
InputStream is = process.getInputStream();
try (InputStreamReader isr = new InputStreamReader(is);
BufferedReader out = new BufferedReader(isr)) {
OutputStream in = process.getOutputStream();
String bufferString = "echo hello" + System.lineSeparator();
byte[] buffer = bufferString.getBytes();
in.write(buffer);
in.flush();
while (isAlive(process)) {
String line = out.readLine();
if (!line.startsWith("PS")) {
System.out.println(line);
}
}
}
}
}
The output produced by the above code is simply:
hello
#talex mentioned the following in his comment to your question:
it is impossible to get rid of PS C://> part
This is not true since you can customize the PowerShell prompt but you wrote that you don't want to see the echo hello in the output and that is not possible and therefore, as #talex mentioned:
you have to filter your input stream yourself
Not displaying echo hello means not displaying the entered command. Imagine that you open a PowerShell window and don't see the command you entered but after you hit ENTER you want to see the output. So I don't think that there is a way to not display echo hello and therefore, in the above code, I don't print lines that start with the PowerShell prompt.
You can find details about launching PowerShell in about_PowerShell_exe
I'm really stuck here, I've read number of articles and answers on stackoverflow, but nothing solved my problem.
I've got the method which runs Selenium Server Hub from cmd.exe using batch file:
public static boolean startGrid() throws IOException {
ProcessBuilder builder = new ProcessBuilder("cmd", "/c", "start", START_GRID_BAT_PATH);
builder.redirectErrorStream(true);
Process process = builder.start();
String out = getCmdOutput(process);
return out.contains("Selenium Grid hub is up and running");
}
The server is started successfully and is running in open cmd.
To make sure that server is up I use the following method to get the output from the cmd:
protected static String getCmdOutput(Process proc) throws java.io.IOException {
String res = "";
BufferedReader stdInput = new BufferedReader(new
InputStreamReader(proc.getInputStream()));
String s = "";
while ((s = stdInput.readLine()) != null) {
res += s;
}
return res;
}
And here is where the problem starts - method hangs at the the line s = stdInput.readLine()) != null
It seems that it can't read any line from the cmd, although I can see that there is number of lines in the output produced by running server.
Any ideas what is wrong?
Maybe the output produced by the server doesn't contain any \r or \n characters but is simply line-wrapped.
Anyhow, you shouldn't rely on the line feed characters when reading the output.
The following example shows how to read binary data from the InputStream of the process non-blocking with java.nio, convert the bytes to chars and write the result to a StringWriter - which is preferrable to using string concatenation.
protected static String getCmdOutput(Process proc, Consumer<String> consumer)
final InputStream source = proc.getInputStream();
final StringWriter sink = new StringWriter();
final ByteBuffer buf = ByteBuffer.allocate(1024);
final CharBuffer chars = CharBuffer.allocate(1024);
final CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
try(final ReadableByteChannel from = Channels.newChannel(source)) {
while (from.read(buf) != -1) {
buf.flip();
decoder.decode(buf, chars, false);
chars.flip();
sink.write(chars.array(), chars.position(), chars.remaining());
buf.compact();
chars.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
return sink.toString();
}
Edit:
As you are starting a process that is continuously producing output, the loop won't finish as long as the process is running. So to continue your parent process, you have to start a thread that is capturing and processing the output of the process continuously
For example
Consumer<String> consumer = ...;
Thread t = new Thread(() -> {
final InputStream source = proc.getInputStream();
final StringWriter sink = new StringWriter();
final ByteBuffer buf = ByteBuffer.allocate(1024);
final CharBuffer chars = CharBuffer.allocate(1024);
final CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
try(final ReadableByteChannel from = Channels.newChannel(source)) {
while (from.read(buf) != -1) {
buf.flip();
decoder.decode(buf, chars, false);
chars.flip();
sink.write(chars.array(), chars.position(), chars.remaining());
forward(sink, consumer); //forward the captured content to a consumer
buf.compact();
chars.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
});
t.start();
private void forward(StringWriter sink, Consumer<String> consumer) {
StringBuffer buf = sink.getBuffer();
int pos = buf.lastIndexOf("\n");
//you may use other options for triggering the process here,
//i.e. on every invocation, on fixed length, etc
if(pos != -1){
consumer.accept(buf.substring(0, pos + 1));
buf.delete(0, pos + 1);
}
//or to send the entire buffer
/*
consumer.accept(sink.toString());
sink.getBuffer().delete(0, sink.getBuffer().length());
*/
}
Depending on your use case, you may not necessarily need a separate thread. It might be ok to process the child-process' output in the main thread and do what you want with the output in the consumer.
It doesn't hang, it just blocks waiting for a line to be read. And after the line has been read, it waits for the next. And so on.
Getting the output stream of a child process should be made in a separate thread, because the child process could in fact hang, than your main thread will hang, too.
An example:
given the instance methods
private static final long WAITTIME_FOR_CHILD = Duration.ofMinutes(5).toNanos();
private static final String START_SEQUENCE = "whatever";
private final Lock lock = new ReentrantLock();
private final Condition waitForStart = lock.newCondition();
private boolean started;
the code that starts the child process
// prepare child process
lock.lock();
try {
Process process = processBuilder.start();
// both standard and error output streams get redirected
pipe(process.getInputStream(), System.out, START_SEQUENCE);
pipe(process.getErrorStream(), System.err, START_SEQUENCE);
// loop because of 'spurious wakeups' - should happen only on Linux,
// but better take care of it
// wait until WAITTIME_FOR_CHILD (default 5 minutes, see above)
long waitTime = WAITTIME_FOR_CHILD;
while (!started) {
// we wait hier until the child process starts or timeout happens
// value <= 0 means timeout
waitTime = waitForStart.awaitNanos(waitTime);
if (waitTime <= 0) {
process.destroyForcibly();
throw new IOException("Prozess xxx couldn't be started");
}
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException("Error during start of xxx", e);
} finally {
lock.unlock();
}
private void getCmdOutput(InputStream in, PrintStream out, final String startSeq) {
startDaemon(() -> {
try (Scanner sc = new Scanner(in)) {
while (sc.hasNextLine()) {
String line = sc.nextLine();
// check your know output sequence
if (line.contains(startSeq)) {
lock.lock();
try {
if (!started) {
started = true;
// waiting for process start finished
waitForStart.signal();
}
} finally {
lock.unlock();
}
}
out.println(line);
}
}
});
}
private void startDaemon(Runnable task) {
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
}
Try replacing while condition like this:
while ((s = stdInput.readLine()) != null && !s.equals("")) {
ProcessBuilder pb =
new ProcessBuilder("myCommand", "myArg1", "myArg2");
Map<String, String> env = pb.environment();
env.put("VAR1", "myValue");
env.remove("OTHERVAR");
env.put("VAR2", env.get("VAR1") + "suffix");
pb.directory(new File("myDir"));
File log = new File("log");
pb.redirectErrorStream(true);
pb.redirectOutput(Redirect.appendTo(log));
Process p = pb.start();
assert pb.redirectInput() == Redirect.PIPE;
assert pb.redirectOutput().file() == log;
assert p.getInputStream().read() == -1;
You can redirect output to a file as oracle documentation offers in basic example:
https://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html
I'm currently working on a project where a client receives shell/console commands from a server, and must execute them.
How do I get Java to run these commands from within either a shell or a command prompt? I'm hoping to be able to disregard the platform type - and not have to specify shell or command prompt - but if I can't, then that's okay.
I must be able to send a sequence of related commands, not just one command. This means that the shell/prompt cannot exit or close between commands.
My current code, as follows, allows for the execution of a sequence of programs, but these commands must somehow be piped into a shell/command prompt, from which the output must be read.
ArrayList<String> comDat = new ArrayList<>();
while(true) {
String input = con.recv();
System.out.println("> " + input);
if(!input.equals("EOF")) comDat.add(input); else {
String[] s = new String[comDat.size()];
for(int i = 0; i < comDat.size(); i++) s[i] = comDat.get(i);
System.out.println("---Command sequence executing---");
Process p = Runtime.getRuntime().exec(s);
p.waitFor();
System.out.println("---ErrorStream output---"); String line = "";
BufferedReader errStream = new BufferedReader(new InputStreamReader(p.getErrorStream()));
while((line = errStream.readLine()) != null) System.out.println("< " + line);
System.out.println("\n---OutputStream output---"); line = "";
BufferedReader outStream = new BufferedReader(new InputStreamReader(p.getInputStream()));
while((line = errStream.readLine()) != null) System.out.println("< " + line);
}
Thread.sleep(200);
}
Thanks for the help!
The basic premise revoles around the fact the dir isn't an external command but is function of cmd.
I would avoid BufferedReaders when reading the output of a process as not all processes use new lines when sending output (such as progress indicators), instead you should read char for char (IMHO).
You should us ProcessBuilder instead of Runtime#exec. It provides better management and allows you to redirect the error stream into the input stream, making it easier to read the input.
import java.io.IOException;
import java.io.InputStream;
public class TestProcessBuilder {
public static void main(String[] args) {
try {
ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "dir");
pb.redirectError();
Process p = pb.start();
InputStreamConsumer isc = new InputStreamConsumer(p.getInputStream());
isc.start();
int exitCode = p.waitFor();
isc.join();
System.out.println("Process terminated with " + exitCode);
} catch (IOException | InterruptedException exp) {
exp.printStackTrace();
}
}
public static class InputStreamConsumer extends Thread {
private InputStream is;
public InputStreamConsumer(InputStream is) {
this.is = is;
}
#Override
public void run() {
try {
int value = -1;
while ((value = is.read()) != -1) {
System.out.print((char)value);
}
} catch (IOException exp) {
exp.printStackTrace();
}
}
}
}