【问题标题】:How to redirect ProcessBuilder's output to a string?如何将 ProcessBuilder 的输出重定向到字符串?
【发布时间】:2013-05-18 19:47:36
【问题描述】:

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

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

我尝试使用ByteArrayOutputStream,但它似乎不起作用。

【问题讨论】:

  • 你是如何使用ByteArrayOutputStream的?
  • “它似乎不起作用”不是问题描述。 ProcessBuilder 没有流,但 Process 有。您不是在启动 ProcessBuilder,而是在使用它创建 Process,然后启动准确地说。

标签: java stream processbuilder


【解决方案1】:

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

【讨论】:

  • 您可能想要添加如何获取错误流,或两者兼而有之。
  • @Fabian 这可以使用processBuilder.inheritIO()轻松完成
  • @Reimeus 这会丢弃原始的行尾字符并替换为当前平台的行尾字符,这可能会有所不同。
  • @Reimeus 即使我得到正确的结果我的行是空的,这是为什么呢?
  • 请注意,如果不重定向错误流,则需要从错误流和输入流中捕获输出,否则进程将挂起。您必须在单独的线程中收集流,否则您的主线程将挂起。在这种情况下,请搜索“gobbler thread”以获取完整解决方案。
【解决方案2】:

你可以这样做:

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

【讨论】:

【解决方案3】:

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

IE:

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

【讨论】:

  • @MarkoBonaci 看到接受的答案。您需要使用 BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));然后用输出构造一个 StringBuilder。
  • 请注意,.inheritIO() 方法适用于 java 7 及以上版本
  • 这会将进程的stdOutstdErr 重定向到Java 的标准输出和错误;它不允许您将输出捕获为字符串。
  • @AndrewRueckert 我正在尝试找出一种使用inheritIO 的方法,并且仍然将输出捕获为字符串。我一直在研究多个 SO 答案,但它们都不能正常工作,输出的缓冲是累积的(没有inheritIO)或者 InputStream 事后是空的。任何指针表示赞赏!
  • @DavidFernandez 我认为你不能以这种方式使用inheritIO;我可能会使用上述解决方案之一来使用输入流收集子进程的 stdout/stderr,并且,每当我从其中一个流中读取一些值时,我只需使用 @ 987654328@ 将其复制到我的程序的输出中。 (虽然对这个“简单”的事情有多困难感到沮丧。)
【解决方案4】:

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

我无耻地从 How to redirect Process Builder's output to a string? 那里偷了它

【讨论】:

  • 您的链接是 this 帖子。这是故意的吗?
  • 这不是故意的,但我想知道是否有人删除了原始 URL,并且在删除目标时不知何故修改了我的链接。我看到社区机器人编辑了我的评论,所以也许这是一个线索。
【解决方案5】:

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

【讨论】:

    【解决方案6】:

    使用 Apache Commons IOUtils 可以在一行中完成:

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

    【讨论】:

    • 之后还是需要关闭流吧?
    • 不确定。我还没有看到任何关闭 InputStream 的 ProcessBuilder 示例。关于 Process 的答案说您仍然应该关闭流......stackoverflow.com/questions/7097697/…
    • 此方法在较新版本的 IOUtils 中已弃用,前提是编码现在是首选用法。例如。字符串输出 = IOUtils.toString(pb.start().getInputStream(), "UTF-8");
    【解决方案7】:

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

    我建议所有需要在 java 中调用外部进程的人使用 Apache Commons Exec 库,而不是重新发明它。

    【讨论】:

    • 非常感谢。我试图使用 ProcessBuilder 执行带有参数的 wkhtmltopdf 程序。在 Windows 中没问题,但在 Linux 服务器中失败了,我无法使用 BufferedReader 获得输出。然而,使用 commons-exec 一切都很容易,并且在两个操作系统中都能正常工作。
    【解决方案8】:

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

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

    【讨论】:

    【解决方案9】:

    解决方案

    • 此代码是您问题的一般解决方案的运行示例:

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

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

    功能

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

    使用与验证

    • 您可以将此代码复制粘贴为一个类,将其编译为 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_;
    }
    

    【讨论】:

      【解决方案10】:

      Java 9 开始,我们终于有了一个内衬:

      ProcessBuilder pb = new ProcessBuilder("pwd");
      Process process = pb.start();
      
      String result = new String(process.getInputStream().readAllBytes());
      

      【讨论】:

        【解决方案11】:

        Java 8 的另一种解决方案:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-07-03
          • 1970-01-01
          相关资源
          最近更新 更多