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

87

我正在使用以下代码启动进程构建器。我想知道如何将其输出重定向到一个String

我正在使用如下代码启动一个进程构建器,我想知道如何将它的输出重定向到一个String

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

我尝试使用 ByteArrayOutputStream,但好像没有起作用。


你如何使用 ByteArrayOutputStream - fGo
“它似乎没有起作用”不是问题描述。“ProcessBuilder”没有流,但“Process”有。你没有启动“ProcessBuilder”,而是使用它来创建一个“Process”,然后启动。要精确。 - user207421
13个回答

98

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

5
您或许希望添加如何获取错误流(error stream)或两者皆可。 - Fabian
7
使用processBuilder.inheritIO()可以轻松实现这一点。 - Reimeus
4
请注意,如果您不重定向错误流,您需要捕获来自错误流和输入流的输出,否则进程将挂起。您必须在单独的线程中收集流,否则您的主线程将挂起。在这种情况下,搜索“gobbler thread”以获取完整解决方案。 - Richard Whitehead
你可以使用Java Stream来改进它:BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String stringBuilder = reader.lines().collect(Collectors.joining(System.lineSeparator())); - Evghenii Orenciuc
有没有办法将此解决方案与等待进程超时结合起来?通常使用Process#waitFor进行超时等待。但我认为这个解决方案会阻塞,直到进程退出,此时使用waitFor已经太晚了。 - Lii
显示剩余3条评论

33

使用 Apache Commons IOUtils,您可以在一行代码中完成:

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

1
流仍然需要在之后关闭,对吗? - jzonthemtn
不确定。我还没有看到任何关闭InputStream的ProcessBuilder示例。有一个关于Process的答案说你仍然应该关闭流... https://dev59.com/W2w05IYBdhLWcg3wrDpS - Daniel
1
在IOUtils的新版本中,此方法已被弃用,现在提供编码是首选用法。例如:String output = IOUtils.toString(pb.start().getInputStream(), "UTF-8"); - jasonoriordan
process.waitFor()在执行yt-dlp --ignore-errors --dump-json http://music.youtube.com/watch?v=SAsJ_n747T4 http://music.youtube.com/watch?v=oDLU3e34G1o命令时出现了卡住的情况,因此我无法读取输出结果。不过,通过使用processBuilder.inheritIO(),我可以看到输出结果。 - Adrian

32

Java 9 开始,我们终于有了一行代码:

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

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

1
有没有办法将此解决方案与等待进程超时结合起来?通常使用Process#waitFor进行超时等待。但我认为readAllBytes将阻塞直到进程退出,此时使用waitFor将为时已晚。 - Lii
我希望有一种方法可以简单地传递一个 outputStream,让 JVM 写入流而不是手动从进程中读取。这将使处理更加高效。 - TheRealChx101
2
这个解决方案是错误的,因为你需要同时读取stdout和stdin,并且需要在不同的线程上进行。原因(正如Richard Whitehead上面已经解释的那样)是进程会将stdout/stderr输出写入固定大小的缓冲区,如果这些缓冲区被填满并且没有被读取,进程将会无限期地阻塞。 - undefined

11

Java 8 示例:

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

我使用这段代码,对于单行或多行结果都很有效。你还可以添加错误流处理程序。


你应该在流处理中使用try-with-resources:https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html - Emily L.
不必使用StringJoiner,你可以直接使用流collect(Collectors.joining(System.getProperty("line.separator"))); - Tamir Adler

9
你可以这样做:
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);
}

7
如果进程向标准错误流(stderr)写入大量数据,可能会导致程序挂起 - 参见 https://dev59.com/lmQn5IYBdhLWcg3wP1JT。 - L.R.

9

只需在进程生成器行中添加.inheritIO();

例如:

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


1
@MarkoBonaci请查看已接受的答案。您需要使用BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));,然后构建一个StringBuilder来获取输出。 - bogdan.rusu
请注意,.inheritIO() 方法适用于 Java 7 及以上版本。 - bogdan.rusu
7
这将把进程的stdOutstdErr重定向到Java的标准输出和错误流,但无法将输出捕获为字符串。 - Andrew Rueckert
1
@AndrewRueckert 我正在尝试找出一种使用inheritIO并仍将输出捕获为字符串的方法。我已经研究了多个SO答案,但没有一个能正常工作,要么是输出缓冲区建立(没有inheritIO),要么是InputStream在事后为空。任何指针都将不胜感激! - David Fernandez
3
我不认为你可以以这种方式使用inheritIO;我可能会使用以上某个解决方案来使用输入流收集子进程的标准输出/标准错误,并在每次从其中一个流中读取一些值时,_也_使用System.{out/stderr}.print将其复制到我的程序输出中。(同时对这个“简单”的事情感到沮丧。) - Andrew Rueckert

5

对于Java 7和8,这应该可以工作:

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

我毫不羞耻地从这里抄袭了:如何将Process Builder的输出重定向到字符串?


您的链接指向帖子。这是有意为之吗? - lafual
1
这并不是故意的,但我想知道是否有人删除了原始URL,并且当目标被删除时我的链接被修改了。我看到社区机器人编辑了我的评论,所以这可能是一个线索。 - Be Kind To New Users

2
尝试处理不同情况(处理stderr和stdout并且不阻塞任何一个,超时后终止进程,正确转义斜杠、引号、特殊字符、空格等),我放弃了,并找到了Apache Commons Exechttps://commons.apache.org/proper/commons-exec/tutorial.html,它似乎非常擅长处理所有这些事情。
我建议每个需要在Java中调用外部进程的人都使用Apache Commons Exec库,而不是重新发明轮子。

1
非常感谢。我尝试使用ProcessBuilder执行带参数的wkhtmltopdf程序。在Windows中一切正常,但在Linux服务器上失败了,并且我无法通过BufferedReader获取输出。然而,使用commons-exec一切都很容易,在两个操作系统中都可以正常工作。 - jmoran

2

Java 8 的另一种解决方案:

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

2
在Java 8中,有一个很好的lines()流可以与String.join和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())
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接