How to redirect ProcessBuilder's output to a string?

JavaStreamProcessbuilder

Java 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";and binaryCommand[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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAnkesh AnandView Question on Stackoverflow
Solution 1 - JavaReimeusView Answer on Stackoverflow
Solution 2 - JavaDanielView Answer on Stackoverflow
Solution 3 - JavakmecppView Answer on Stackoverflow
Solution 4 - JavaGreg TView Answer on Stackoverflow
Solution 5 - JavafGoView Answer on Stackoverflow
Solution 6 - JavathoulihaView Answer on Stackoverflow
Solution 7 - JavaBe Kind To New UsersView Answer on Stackoverflow
Solution 8 - JavaNovaterataView Answer on Stackoverflow
Solution 9 - JavaL.R.View Answer on Stackoverflow
Solution 10 - Javaa.t.View Answer on Stackoverflow
Solution 11 - JavabwregaView Answer on Stackoverflow
Solution 12 - JavaMahozadView Answer on Stackoverflow