How to redirect ProcessBuilder's output to a string?
JavaStreamProcessbuilderJava Problem Overview
I am using the following code to start a process builder.I want to know how can I redirect its output to a String
.
ProcessBuilder pb = new ProcessBuilder(
System.getProperty("user.dir") + "/src/generate_list.sh", filename);
Process p = pb.start();
I tried using ByteArrayOutputStream
but it didn't seem to work.
Java Solutions
Solution 1 - Java
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();
Solution 2 - Java
Using Apache Commons IOUtils you can do it in one line:
ProcessBuilder pb = new ProcessBuilder("pwd");
String output = IOUtils.toString(pb.start().getInputStream(), StandardCharsets.UTF_8);
Solution 3 - Java
As of Java 9, we finally have a one liner:
ProcessBuilder pb = new ProcessBuilder("pwd");
Process process = pb.start();
String result = new String(process.getInputStream().readAllBytes());
Solution 4 - Java
Java 8 example:
public static String runCommandForOutput(List<String> params) {
ProcessBuilder pb = new ProcessBuilder(params);
Process p;
String result = "";
try {
p = pb.start();
final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
reader.lines().iterator().forEachRemaining(sj::add);
result = sj.toString();
p.waitFor();
p.destroy();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
Usage:
List<String> params = Arrays.asList("/bin/sh", "-c", "cat /proc/cpuinfo");
String result = runCommandForOutput(params);
I use this exact code and it works well for single or multiple line results. You could add an error stream handler as well.
Solution 5 - Java
You might do something like this:
private static BufferedReader getOutput(Process p) {
return new BufferedReader(new InputStreamReader(p.getInputStream()));
}
private static BufferedReader getError(Process p) {
return new BufferedReader(new InputStreamReader(p.getErrorStream()));
}
...
Process p = Runtime.getRuntime().exec(commande);
BufferedReader output = getOutput(p);
BufferedReader error = getError(p);
String ligne = "";
while ((ligne = output.readLine()) != null) {
System.out.println(ligne);
}
while ((ligne = error.readLine()) != null) {
System.out.println(ligne);
}
Solution 6 - Java
Just add .inheritIO();
to the process builder line.
IE:
ProcessBuilder pb = new ProcessBuilder(script.sh).inheritIO();
Solution 7 - Java
For Java 7 and 8 this should work:
private String getInputAsString(InputStream is)
{
try(java.util.Scanner s = new java.util.Scanner(is))
{
return s.useDelimiter("\\A").hasNext() ? s.next() : "";
}
}
Then in your code, do this:
String stdOut = getInputAsString(p.getInputStream());
String stdErr = getInputAsString(p.getErrorStream());
I shamelessly stole that from: https://stackoverflow.com/questions/16714127/how-to-redirect-process-builders-output-to-a-string
Solution 8 - Java
In java 8 there is a nice lines() stream you can combine with String.join and System.lineSeparator():
try (BufferedReader outReader = new BufferedReader(new InputStreamReader(p.getInputStream()))
{
return String.join(System.lineSeparator(), outReader.lines().collect(toList()));
\\ OR using jOOλ if you like reduced verbosity
return Seq.seq(outReader.lines()).toString(System.lineSeparator())
}
Solution 9 - Java
After trying to handle different cases (handling both stderr and stdout and not blocking any of these, terminate process after timeout, properly escaping slashes, quotation marks, special characters, spaces, .... ) I gave up and found Apache Commons Exec https://commons.apache.org/proper/commons-exec/tutorial.html that seems to be doing all these things pretty well.
I do recommend everyone who needs to invoke external process in java to use Apache Commons Exec library instead of reinventing it again.
Solution 10 - Java
Solutions
- This code is a running example for the general solution to your question:
> How to redirect Process Builder's output to a string?
- Credits go to Greg T, after trying multiple solutions to run various commands and capture their outputs Greg T's answer contained the essence of the particular solution. I hope the general example is of use for someone combining multiple requirements whilst capturing the output.
- To obtain your particular solution you can uncomment
ProcessBuilder pb = new ProcessBuilder(System.getProperty("user.dir")+"/src/generate_list.sh", filename);
, uncomment the line, and comment out:ProcessBuilder processBuilder = new ProcessBuilder(commands);
.
Functionality
- It is a working example that executes command
echo 1
and returns the output as a String. - I also added setting a working path and an environment variable, which is not required for your particular example so you can delete it.
Usage & verification
- You can copy paste this code as a class, compile it to jar and run it.
- It is verified in WSL Ubuntu 16.04.
- Setting the workdirectory is verified by setting
binaryCommand[0]="touch";
andbinaryCommand[1]="1";
, re-compiling and running the.jar
file.
Limitations
- If the pipe is full (due to a "too large" output), the code hangs.
Code
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Map;
import java.util.StringJoiner;
public class GenerateOutput {
/**
* This code can execute a command and print the output accompanying that command.
* compile this project into a .jar and run it with for example:
* java -jar readOutputOfCommand.jar
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
boolean answerYes = false; // no yes answer to any command prompts is needed.
// to execute a command with spaces in it in terminal, put them in an array of Strings.
String[] binaryCommand = new String[2];
// write a command that gives a binary output:
binaryCommand[0] = "echo";
binaryCommand[1] = "1";
// pass the commands to a method that executes them
System.out.println("The output of the echo command = "+executeCommands(binaryCommand,answerYes));
}
/**
* This executes the commands in terminal.
* Additionally it sets an environment variable (not necessary for your particular solution)
* Additionally it sets a working path (not necessary for your particular solution)
* @param commandData
* @param ansYes
* @throws Exception
*/
public static String executeCommands(String[] commands,Boolean ansYes) throws Exception {
String capturedCommandOutput = null;
System.out.println("Incoming commandData = "+Arrays.deepToString(commands));
File workingDirectory = new File("/mnt/c/testfolder b/");
// create a ProcessBuilder to execute the commands in
ProcessBuilder processBuilder = new ProcessBuilder(commands);
//ProcessBuilder processBuilder = new ProcessBuilder(System.getProperty("user.dir")+"/src/generate_list.sh", "a");
// this is not necessary but can be used to set an environment variable for the command
processBuilder = setEnvironmentVariable(processBuilder);
// this is not necessary but can be used to set the working directory for the command
processBuilder.directory(workingDirectory);
// execute the actual commands
try {
Process process = processBuilder.start();
// capture the output stream of the command
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
reader.lines().iterator().forEachRemaining(sj::add);
capturedCommandOutput = sj.toString();
System.out.println("The output of this command ="+ capturedCommandOutput);
// here you connect the output of your command to any new input, e.g. if you get prompted for `yes`
new Thread(new SyncPipe(process.getErrorStream(), System.err)).start();
new Thread(new SyncPipe(process.getInputStream(), System.out)).start();
PrintWriter stdin = new PrintWriter(process.getOutputStream());
//This is not necessary but can be used to answer yes to being prompted
if (ansYes) {
System.out.println("WITH YES!");
stdin.println("yes");
}
// write any other commands you want here
stdin.close();
// this lets you know whether the command execution led to an error(!=0), or not (=0).
int returnCode = process.waitFor();
System.out.println("Return code = " + returnCode);
} catch (IOException e1) {
e1.printStackTrace();
}
return capturedCommandOutput;
}
/**
* source: https://stackoverflow.com/questions/7369664/using-export-in-java
* @param processBuilder
* @param varName
* @param varContent
* @return
*/
private static ProcessBuilder setEnvironmentVariable(ProcessBuilder processBuilder){
String varName = "variableName";
String varContent = "/mnt/c/testfolder a/";
Map<String, String> env = processBuilder.environment();
System.out.println("Setting environment variable "+varName+"="+varContent);
env.put(varName, varContent);
processBuilder.environment().put(varName, varContent);
return processBuilder;
}
}
class SyncPipe implements Runnable
{
/**
* This class pipes the output of your command to any new input you generated
* with stdin. For example, suppose you run cp /mnt/c/a.txt /mnt/b/
* but for some reason you are prompted: "do you really want to copy there yes/no?
* then you can answer yes since your input is piped to the output of your
* original command. (At least that is my practical interpretation might be wrong.)
* @param istrm
* @param ostrm
*/
public SyncPipe(InputStream istrm, OutputStream ostrm) {
istrm_ = istrm;
ostrm_ = ostrm;
}
public void run() {
try
{
final byte[] buffer = new byte[1024];
for (int length = 0; (length = istrm_.read(buffer)) != -1; )
{
ostrm_.write(buffer, 0, length);
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
private final OutputStream ostrm_;
private final InputStream istrm_;
}
Solution 11 - Java
Another solution for Java 8:
BufferedReader stdOut = new BufferedReader(new InputStreamReader(process.getInputStream()));
String stdOutStr = stdOut.lines()
.collect(Collectors.joining(System.lineSeparator()));
Solution 12 - Java
This is for Kotlin users ending up here:
val myCommand = "echo Hello"
val process = ProcessBuilder()
.command(myCommand.split(" "))
.directory("./")
.redirectOutput(Redirect.INHERIT)
.redirectError(Redirect.INHERIT)
.start()
.waitFor(60, TimeUnit.SECONDS)
val result = process.inputStream.bufferedReader().readText()
println(result) // Prints Hello