How to Redirect Processbuilder's Output to a String

How to redirect ProcessBuilder's output to a string?

Read from the InputStream. You can append the output to a StringBuilder:

BufferedReader reader = 
new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder builder = new StringBuilder();
String line = null;
while ( (line = reader.readLine()) != null) {
builder.append(line);
builder.append(System.getProperty("line.separator"));
}
String result = builder.toString();

InheritIO and redirecting stdout

System.setOut changes the Java process’s standard output. There’s no guarantee it will affect child processes.

Regardless, it’s probably not a good idea to alter a global setting just to capture the output of a single process, especially if you expect your code to run amidst other other code (libraries, appications, etc.).

Solution: Don’t try to hack System.out. Read from the Process’s stdout InputStream:

ProcessBuilder builder = new ProcessBuilder().command("where", "where").inheritIO();
builder.redirectOutput(ProcessBuilder.Redirect.PIPE);
Process process = builder.start();

String output;
try (InputStream processStdOut = process.getInputStream()) {
output = new String(processStdOut.readAllBytes());
}

process.waitFor();

ProcessBuilder redirecting output

Shell redirection operators are unknown to ProcessBuilder. Put your command in a shell script and execute it, as shown here. Alternatively, use bash -c, as shown here.

ProcessBuilder: Forwarding stdout and stderr of started processes without blocking the main thread

For Java 7 and later, see Evgeniy Dorofeev's answer.

For Java 6 and earlier, create and use a StreamGobbler:

StreamGobbler errorGobbler = 
new StreamGobbler(p.getErrorStream(), "ERROR");

// any output?
StreamGobbler outputGobbler =
new StreamGobbler(p.getInputStream(), "OUTPUT");

// start gobblers
outputGobbler.start();
errorGobbler.start();

...

private class StreamGobbler extends Thread {
InputStream is;
String type;

private StreamGobbler(InputStream is, String type) {
this.is = is;
this.type = type;
}

@Override
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null)
System.out.println(type + "> " + line);
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}
}

Java/Kotlin way to redirect command output to both stdout and String

Here is the ProcessBuilder solution, which I initially wanted to avoid. It does the job though it is bulky. Let me know if a better API is made available!

var logs:String = ""
runCatching {
var command:List<String> = listOf("command", "arg")
parameters.params?.let {command += it} // dynamic args
ProcessBuilder(command)
.directory(File(scriptRoot))
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectErrorStream(true) // Merges stderr into stdout
.start().also { process ->
withContext(Dispatchers.IO) { // More info on this context switching : https://elizarov.medium.com/blocking-threads-suspending-coroutines-d33e11bf4761
launch {
process.inputStream.bufferedReader().run {
while (true) { // Breaks when readLine returns null
readLine()?.let { line ->
logger.trace(line) // realtime logging
logs += "$line\n" // record
} ?: break
}
}
}

process.waitFor(60, TimeUnit.MINUTES)
if(process.isAlive) {
logs += "TIMEOUT occurred".also { logger.warn(it) } + "\n"
process.destroy()
}
}
}
}.onSuccess { process ->
if(process.exitValue() == 0) {
// completed with success
} else {
// completed with failure
}

}.onFailure { ex ->
logs = ex.stackTraceToString()
}

// Logs are available in $logs

Redirecting the output of a process into the input of another process using ProcessBuilder in Java

static ProcessBuilder.Redirect INHERIT Indicates that subprocess I/O
source or destination will be the same as those of the current
process.

static ProcessBuilder.Redirect PIPE Indicates that subprocess I/O
will be connected to the current Java process over a pipe.

So I don't think one of these will help you redirecting the output of one process to
the input of another process. You have to implement it yourself.

Implementation:

public class RedirectStreams {
public RedirectStreams(Process process1, Process process2) {
final Process tmpProcess1 = process1;
final Process tmpProcess2 = process2;
new Thread(new Runnable() {
@Override
public void run() {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(tmpProcess1.getInputStream()));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(tmpProcess2.getOutputStream()));
String lineToPipe;

try {

while ((lineToPipe = bufferedReader.readLine()) != null){
System.out.println("Output process1 / Input process2:" + lineToPipe);
bufferedWriter.write(lineToPipe + '\n');
bufferedWriter.flush();
}

} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}

}

This one can surely be designed nicer
and I haven't tested, if it runs's safe, but it does the job.

Usage:

RedirectStreams redirectStreams = new RedirectStreams(process1,process2);

Test:

public class ProcessPipeTest {
@Test public void testPipe(){
try {

Process process1 = new ProcessBuilder("/bin/bash").start();
Process process2 = new ProcessBuilder("/bin/bash").start();

RedirectStreams redirectStreams = new RedirectStreams(process1,process2);


BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(process1.getOutputStream()));
String command = "echo echo echo";
System.out.println("Input process1: " + command);
bufferedWriter.write(command + '\n');
bufferedWriter.close();


BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process2.getInputStream()));
String actualOutput = bufferedReader.readLine();
System.out.println("Output process2: " + actualOutput);
assertEquals("echo",actualOutput);

} catch (IOException e) {
e.printStackTrace();
}
}
}

Output:

Input process1: echo echo echo

Output process1 / Input process2:echo echo

Output process2: echo

How to redirect the output to a java stream using java.lang.ProcessBuilder

How to do :

When you will start you process you will be able to get a OutputStream via the Process object.

In the documentation of ProcessBuilder you can find the method Process start().

With the Process object you will be albe to get the OutputStream via the method abstract OutputStream getOutputStream().

Documentation :

  • https://docs.oracle.com/javase/8/docs/api/java/lang/ProcessBuilder.html
  • https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html

Exemple :

ProcessBuilder processBuilder = ProcessBuilder("/here/your/command1", "/here/your/command2");
Process process = processBuilder.start();
OutputStream outputSteeam = process.getOutputStream();

Process from ProcessBuilder not directing output to string

You're making multiple mistakes in your process builder code; you need to fix them all before this works.

inheritIO() literally means: forget .getInputStream() and redirect and all that jazz, just inherit the standard in/out/err of the java process itself. Which you specifically do not want, so get rid of that.

But that's not enough.

Then, you first call .waitFor(), and THEN you open the inputstream. That doesn't work - once the process has died there's no inputstream for you to get.

You need to start the process, then read the data, and THEN you can waitFor if you must.

Here is some example code from one of my projects:

private static <E extends Throwable> String exec0(String description, Function<String, E> exceptionFactory, String... args) throws E {

Process process; try {
process = new ProcessBuilder(args).redirectErrorStream(true).start();
} catch (IOException e) {
throw rethrow(e, "Cannot execute " + description, exceptionFactory);
}

String output; try {
@Cleanup var in = process.getInputStream();
output = new String(in.readAllBytes(), StandardCharsets.US_ASCII);
} catch (IOException e) {
throw rethrow(e, "Cannot stream results of " + description, exceptionFactory);
}

int v; try {
v = process.waitFor();
} catch (InterruptedException e) {
throw rethrow(e, "Executing " + description + " interrupted", exceptionFactory);
}

if (v != 0) throw exceptionFactory.apply(description + " returned with error code " + v + ": " + output);
return output;
}

private static <E extends Throwable> E rethrow(Throwable source, String prefix, Function<String, E> exceptionFactory) {

String msg = source.getMessage() == null ? prefix : (prefix + ": " + source.getMessage());
E ex = exceptionFactory.apply(msg);
if (source.getCause() != null) ex.initCause(source.getCause());
ex.setStackTrace(source.getStackTrace());
return ex;
}

It does some fancy footwork to let you automatically throw the appropriate exception for your API design (generally 'I run an app on the host OS' is supposed to be an implementation detail, therefore the specific exceptions that ProcessBuilder throws are not appropriate to just blindly pass on; they need to be restated in terms of your API.

If you don't care about it, you can easily rip it out.

The key parts are to open the input stream first (don't call waitFor until after), and reading all the bytes out. in.readAllBytes() is a rather convenient way to do that.

NB: @Cleanup is lombok cleanup. If you don't use that, then use try (var in = x.getInputStream()) { ... } - you need to safe-close that thing.



Related Topics



Leave a reply



Submit