如何将 ProcessBuilder 的输出重定向到字符串?

我使用下面的代码来启动一个流程构建器。我想知道如何将它的输出重定向到 String

ProcessBuilder pb = new ProcessBuilder(
System.getProperty("user.dir") + "/src/generate_list.sh", filename);
Process p = pb.start();

我尝试使用 ByteArrayOutputStream,但似乎没有工作。

115321 次浏览

读取 InputStream。你可以将输出附加到 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();

你可以这样做:

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);
}

只需将 .inheritIO();添加到流程构建器行。

IE:

ProcessBuilder pb = new ProcessBuilder(script.sh).inheritIO();

对于 Java7和 Java8,这应该是可行的:

private String getInputAsString(InputStream is)
{
try(java.util.Scanner s = new java.util.Scanner(is))
{
return s.useDelimiter("\\A").hasNext() ? s.next() : "";
}
}

然后在你的代码中,这样做:

String stdOut = getInputAsString(p.getInputStream());
String stdErr = getInputAsString(p.getErrorStream());

我厚颜无耻地从 如何将 ProcessBuilder 的输出重定向到字符串?偷来的

在 java 8中,有一个很好的 lines ()流,可以与 String.join 和 System.linePartiator ()组合使用:

    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())
}

使用 Apache Commons 白痴,您可以在一行中完成:

ProcessBuilder pb = new ProcessBuilder("pwd");
String output = IOUtils.toString(pb.start().getInputStream(), StandardCharsets.UTF_8);

在尝试处理不同的情况之后(同时处理 stderr 和 stdout 并且不阻塞任何一个,在超时之后终止进程,正确转义斜杠、引号、特殊字符、空格,... ...) ,我放弃了,发现 Apache Commons Exec https://commons.apache.org/proper/commons-exec/tutorial.html似乎在所有这些方面都做得很好。

我建议每个需要在 Java 中调用外部进程的人使用 ApacheCommons Exec 库,而不是重新创建它。

Java8例子:

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;
}

用法:

List<String> params = Arrays.asList("/bin/sh", "-c", "cat /proc/cpuinfo");
String result = runCommandForOutput(params);

我使用这个精确的代码,并且它适用于单行或多行结果。您也可以添加错误流处理程序。

解决方案

  • 这段代码是您的问题的通用解决方案的一个运行示例:

如何将 ProcessBuilder 的输出重定向到字符串?

  • 在尝试了多个解决方案来运行各种命令并获取其输出之后,Greg T 获得了荣誉。 Greg T 的答案包含了特定解决方案的精髓。我希望这个通用示例对于那些在捕获输出的同时结合多个需求的人是有用的。
  • 要获得特定的解决方案,您可以取消注释 ProcessBuilder pb = new ProcessBuilder(System.getProperty("user.dir")+"/src/generate_list.sh", filename);,取消注释该行,并注释掉: ProcessBuilder processBuilder = new ProcessBuilder(commands);

功能

  • 这是一个工作示例,它执行命令 echo 1并将输出作为 String 返回。
  • 我还添加了一个工作路径和一个环境变量,这对于您的特定示例来说是不需要的,因此您可以删除它。

使用与验证

  • 您可以将此代码复制粘贴为一个类,将其编译为 jar 并运行它。
  • 它在 WSL Ubuntu 16.04中进行了验证。
  • 通过设置 binaryCommand[0]="touch";binaryCommand[1]="1";、重新编译和运行 .jar文件来验证工作目录的设置。

限制

  • 如果管道已满(由于输出“太大”) ,则代码挂起。

密码

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_;
}

爪哇9开始,我们终于有了一句话:

ProcessBuilder pb = new ProcessBuilder("pwd");
Process process = pb.start();


String result = new String(process.getInputStream().readAllBytes());

Java8的另一个解决方案:

BufferedReader stdOut = new BufferedReader(new InputStreamReader(process.getInputStream()));
String stdOutStr = stdOut.lines()
.collect(Collectors.joining(System.lineSeparator()));

这是为 科特林用户准备的:

val myCommand = "java -version"
val process = ProcessBuilder()
.command(myCommand.split(" "))
// .directory(File("./")) // Working directory
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.redirectError(ProcessBuilder.Redirect.INHERIT)
.start()
process.waitFor(60, TimeUnit.SECONDS)
val result = process.inputStream.reader().readText()
println(result)