admin管理员组

文章数量:1441542

ProcessBuilder API 指南

1. 概述

Process API提供了一种在 Java 中执行操作系统命令的强大方法。但是,它有几个选项,可能会使其使用起来很麻烦。

在本教程中,我们将看看 Java 如何使用_ProcessBuilder_API 缓解这种情况。

2.进程生成器接口

提供了用于创建和配置操作系统进程的方法。每个ProcessBuilder实例都允许我们管理流程属性的集合。然后,我们可以使用这些给定属性启动一个新流程

以下是我们可以使用此 API 的一些常见场景:

  • 查找当前 Java 版本
  • 为我们的环境设置自定义键值映射
  • 更改运行 shell 命令的工作目录
  • 将输入和输出流重定向到自定义替换
  • 继承当前 JVM 进程的两个流
  • 从 Java 代码执行 shell 命令

我们将在后面的部分中查看每个实例的实际示例。

但在深入研究工作代码之前,让我们看一下此 API 提供了什么样的功能。

2.1. 方法总结

在本节中,我们将退后一步,简要介绍ProcessBuilder类中最重要的方法。当我们稍后深入研究一些真实示例时,这将对我们有所帮助:

代码语言:javascript代码运行次数:0运行复制
ProcessBuilder(String... command)Copy

要使用指定的操作系统程序和参数创建新的进程生成器,我们可以使用此方便的构造函数。

代码语言:javascript代码运行次数:0运行复制
directory(File directory)Copy

我们可以通过调用directory方法并传递File对象来覆盖当前进程的默认工作目录。默认情况下,当前工作目录设置为user.dir系统属性返回的值。

代码语言:javascript代码运行次数:0运行复制
environment()Copy

如果我们想获取当前的环境变量,我们可以简单地调用环境方法。它使用System.getenv() 但作为Map 返回当前进程环境的副本。

代码语言:javascript代码运行次数:0运行复制
inheritIO()Copy

如果我们想指定子进程标准 I/O 的源和目标应该与当前 Java 进程的源和目标相同,我们可以使用inheritIO方法。

代码语言:javascript代码运行次数:0运行复制
redirectInput(File file), redirectOutput(File file), redirectError(File file)Copy

当我们想要将流程生成器的标准输入、输出和错误目标重定向到文件时,我们可以使用这三种类似的重定向方法。

代码语言:javascript代码运行次数:0运行复制
start()Copy

最后但并非最不重要的一点是,要使用我们配置的内容启动一个新进程,我们只需调用start()。

我们应该注意,这个类是不同步的。例如,如果我们有多个线程同时访问ProcessBuilder实例,则必须在外部管理同步。

3. 示例

现在我们已经对_ProcessBuilder_API 有了基本的了解,让我们逐步了解一些示例。

3.1. 使用ProcessBuilder打印 Java 版本

在第一个示例中,我们将使用一个参数运行java命令以获取版本。

代码语言:javascript代码运行次数:0运行复制
Process process = new ProcessBuilder("java", "-version").start();Copy

首先,我们创建ProcessBuilder对象,将命令和参数值传递给构造函数。接下来,我们使用start() 方法启动进程来获取一个 Process对象。

现在让我们看看如何处理输出:

代码语言:javascript代码运行次数:0运行复制
List<String> results = readOutput(process.getInputStream());

assertThat("Results should not be empty", results, is(not(empty())));
assertThat("Results should contain java version: ", results, hasItem(containsString("java version")));

int exitCode = process.waitFor();
assertEquals("No errors should be detected", 0, exitCode);Copy

在这里,我们正在读取流程输出并验证内容是否符合我们的预期。在最后一步中,我们使用process.waitFor()等待进程完成。

流程完成后,返回值会告诉我们流程是否成功。

要记住的几个要点:

  • 参数必须按正确的顺序排列
  • 此外,在此示例中,使用了默认的工作目录和环境
  • 我们故意在读取输出之前不调用process.waitFor(),因为输出缓冲区可能会使进程停止
  • 我们假设java命令可通过PATH变量获得

3.2. 使用修改后的环境启动进程

在下一个示例中,我们将了解如何修改工作环境。

但在我们这样做之前,让我们先看一下我们可以在默认环境中找到的信息类型:

代码语言:javascript代码运行次数:0运行复制
ProcessBuilder processBuilder = new ProcessBuilder();        
Map<String, String> environment = processBuilder.environment();
environment.forEach((key, value) -> System.out.println(key + value));Copy

这只会打印出默认提供的每个变量条目:

代码语言:javascript代码运行次数:0运行复制
PATH/usr/bin:/bin:/usr/sbin:/sbin
SHELL/bin/bash
...Copy

现在,我们将向ProcessBuilder对象添加新的环境变量,并运行命令来输出其值:

代码语言:javascript代码运行次数:0运行复制
environment.put("GREETING", "Hola Mundo");

processBuildermand("/bin/bash", "-c", "echo $GREETING");
Process process = processBuilder.start();Copy

让我们分解这些步骤来了解我们所做的工作:

  • 将一个名为“GREETING”的变量(值为“Hola Mundo”)添加到我们的环境中,这是一个标准的Map<String,String>
  • 这一次,我们没有使用构造函数,而是通过命令(String...命令)方法直接。
  • 然后,我们按照前面的示例开始我们的过程。

为了完成示例,我们验证输出是否包含我们的问候语:

代码语言:javascript代码运行次数:0运行复制
List<String> results = readOutput(process.getInputStream());
assertThat("Results should not be empty", results, is(not(empty())));
assertThat("Results should contain java version: ", results, hasItem(containsString("Hola Mundo")));Copy

3.3. 使用修改后的工作目录启动进程

有时更改工作目录可能很有用。在下一个示例中,我们将了解如何做到这一点:

代码语言:javascript代码运行次数:0运行复制
@Test
public void givenProcessBuilder_whenModifyWorkingDir_thenSuccess() 
  throws IOException, InterruptedException {
    ProcessBuilder processBuilder = new ProcessBuilder("/bin/sh", "-c", "ls");

    processBuilder.directory(new File("src"));
    Process process = processBuilder.start();

    List<String> results = readOutput(process.getInputStream());
    assertThat("Results should not be empty", results, is(not(empty())));
    assertThat("Results should contain directory listing: ", results, contains("main", "test"));

    int exitCode = process.waitFor();
    assertEquals("No errors should be detected", 0, exitCode);
}Copy

在上面的例子中,我们使用方便的方法目录(File directory)将工作目录设置为项目的src_dir。然后,我们运行一个简单的目录列表命令,并检查输出是否包含子目录_maintest

3.4. 重定向标准输入和输出

在现实世界中,我们可能希望在日志文件中捕获正在运行的进程的结果以进行进一步分析。幸运的是,_ProcessBuilder_API对此具有内置的支持,正如我们将在本例中看到的那样。

默认情况下,我们的进程从管道读取输入。我们可以通过Process.getOutputStream() 返回的输出流访问此管道。

但是,正如我们稍后将看到的,标准输出可能会使用方法重定向到另一个源,例如文件。在这种情况下,getOutputStream() 将返回一个ProcessBuilder.NullOutputStream。

让我们回到原始示例来打印出 Java 版本。但这次让我们将输出重定向到日志文件而不是标准输出管道:

代码语言:javascript代码运行次数:0运行复制
ProcessBuilder processBuilder = new ProcessBuilder("java", "-version");

processBuilder.redirectErrorStream(true);
File log = folder.newFile("java-version.log");
processBuilder.redirectOutput(log);

Process process = processBuilder.start();Copy

在上面的示例中,我们创建了一个名为 log 的新临时文件,并告诉我们的ProcessBuilder将输出重定向到此文件目标。

在最后一个代码段中,我们只需检查getInputStream() 是否确实为,并且文件的内容是否符合预期:

代码语言:javascript代码运行次数:0运行复制
assertEquals("If redirected, should be -1 ", -1, process.getInputStream().read());
List<String> lines = Files.lines(log.toPath()).collect(Collectors.toList());
assertThat("Results should contain java version: ", lines, hasItem(containsString("java version")));Copy

现在让我们看一下这个例子的细微变化。例如,当我们希望附加到日志文件而不是每次都创建一个新文件时:

代码语言:javascript代码运行次数:0运行复制
File log = tempFolder.newFile("java-version-append.log");
processBuilder.redirectErrorStream(true);
processBuilder.redirectOutput(Redirect.appendTo(log));Copy

同样重要的是要提到对redirectErrorStream(true)的调用。如果出现任何错误,错误输出将合并到正常进程输出文件中。

当然,我们可以为标准输出和标准错误输出指定单独的文件:

代码语言:javascript代码运行次数:0运行复制
File outputLog = tempFolder.newFile("standard-output.log");
File errorLog = tempFolder.newFile("error.log");

processBuilder.redirectOutput(Redirect.appendTo(outputLog));
processBuilder.redirectError(Redirect.appendTo(errorLog));Copy

3.5. 继承当前进程的 I/O

在这个倒数第二个示例中,我们将看到inheritIO() 方法的实际应用。当我们想要将子进程 I/O 重定向到当前进程的标准 I/O 时,可以使用此方法:

代码语言:javascript代码运行次数:0运行复制
@Test
public void givenProcessBuilder_whenInheritIO_thenSuccess() throws IOException, InterruptedException {
    ProcessBuilder processBuilder = new ProcessBuilder("/bin/sh", "-c", "echo hello");

    processBuilder.inheritIO();
    Process process = processBuilder.start();

    int exitCode = process.waitFor();
    assertEquals("No errors should be detected", 0, exitCode);
}Copy

在上面的示例中,通过使用inheritIO() 方法,我们在 IDE 的控制台中看到一个简单的命令的输出。

在下一节中,我们将看看Java 9中对_ProcessBuilder_API进行了哪些添加。

4. Java 9 新增内容

Java 9 在_ProcessBuilder_API 中引入了管道的概念:

代码语言:javascript代码运行次数:0运行复制
public static List<Process> startPipeline​(List<ProcessBuilder> builders)
Copy

使用startPipeline方法,我们可以传递ProcessBuilder对象的列表。然后,此静态方法将为每个进程生成器启动一个进程。因此,创建一个流程管道,这些流程通过其标准输出和标准输入流链接。

例如,如果我们想运行这样的东西:

代码语言:javascript代码运行次数:0运行复制
find . -name *.java -type f | wc -lCopy

我们要做的是为每个隔离的命令创建一个流程构建器,并将它们组合到一个管道中:

代码语言:javascript代码运行次数:0运行复制
@Test
public void givenProcessBuilder_whenStartingPipeline_thenSuccess()
  throws IOException, InterruptedException {
    List builders = Arrays.asList(
      new ProcessBuilder("find", "src", "-name", "*.java", "-type", "f"), 
      new ProcessBuilder("wc", "-l"));

    List processes = ProcessBuilder.startPipeline(builders);
    Process last = processes.get(processes.size() - 1);

    List output = readOutput(last.getInputStream());
    assertThat("Results should not be empty", output, is(not(empty())));
}Copy

在此示例中,我们将搜索src目录中的所有 java 文件,并将结果管道传输到另一个进程中以对其进行计数。

5. 结论

总而言之,在本教程中,我们详细探讨了_java.lang.ProcessBuilder_API。

首先,我们首先解释了 API 可以做什么,并总结了最重要的方法。

接下来,我们看了一些实际的例子。最后,我们研究了 Java 9 中 API 中引入了哪些新增功能。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2019-06-06,如有侵权请联系 cloudcommunity@tencent 删除教程进程javaapiprocessbuilder

本文标签: ProcessBuilder API 指南