# Java `ProcessBuilder`教程

原文：http://zetcode.com/java/processbuilder/

Java `ProcessBuilder`教程显示了如何使用`ProcessBuilder`创建操作系统进程。

## `ProcessBuilder`

`ProcessBuilder`用于创建操作系统进程。 其`start()`方法创建具有以下属性的新`Process`实例：

*   命令
*   环境
*   工作目录
*   输入来源
*   标准输出和标准错误输出的目标
*   `redirectErrorStream`

## `ProcessBuilder`运行程序

用`command()`执行程序。 使用`waitFor()`，我们可以等待过程完成。

`ExecuteProgram.java`

```java
package com.zetcode;

import java.io.IOException;

public class ExecuteProgram {

    public static void main(String[] args) throws IOException, InterruptedException {

        var processBuilder = new ProcessBuilder();

        processBuilder.command("notepad.exe");

        var process = processBuilder.start();

        var ret = process.waitFor();

        System.out.printf("Program exited with code: %d", ret);
    }
}

```

该程序执行 Windows 记事本应用。 它返回其退出代码。

## `ProcessBuilder`命令输出

以下示例执行命令并显示其输出。

`ProcessBuilderEx.java`

```java
package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class ProcessBuilderEx {

    public static void main(String[] args) throws IOException {

        var processBuilder = new ProcessBuilder();

        processBuilder.command("cal", "2019", "-m 2");

        var process = processBuilder.start();

        try (var reader = new BufferedReader(
            new InputStreamReader(process.getInputStream()))) {

            String line;

            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }

        }
    }
}

```

该示例运行 Linux `cal`命令。

```java
processBuilder.command("cal", "2019", "-m 2");

```

`command()`执行`cal`程序。 其他参数是程序的选项。 为了在 Windows 机器上运行命令，我们可以使用以下命令：`processBuilder.command("cmd.exe", "/c", "ping -n 3 google.com")`。

```java
var process = processBuilder.start();

```

`start()`启动了该过程。

```java
try (var reader = new BufferedReader(
    new InputStreamReader(process.getInputStream()))) {

```

使用`getInputStream()`方法，我们从流程的标准输出中获取输入流。

```java
February 2019      
Su Mo Tu We Th Fr Sa  
                1  2  
    3  4  5  6  7  8  9  
10 11 12 13 14 15 16  
17 18 19 20 21 22 23  
24 25 26 27 28 

```

这是输出。

## `ProcessBuilder`重定向输出

使用`redirectOutput()`，我们可以重定向流程构建器的标准输出目的地。

`RedirectOutputEx.java`

```java
package com.zetcode;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

public class RedirectOutputEx {

    public static void main(String[] args) throws IOException {

        var homeDir = System.getProperty("user.home");

        var processBuilder = new ProcessBuilder();

        processBuilder.command("cmd.exe", "/c", "date /t");

        var fileName = new File(String.format("%s/Documents/tmp/output.txt", homeDir));

        processBuilder.redirectOutput(fileName);

        var process = processBuilder.start();

        try (var reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {

            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}

```

该程序将构建器的输出重定向到文件。 它运行 Windows `date`命令。

```java
processBuilder.redirectOutput(fileName);

```

我们将流程构建器的标准输出重定向到文件。

```java
try (var reader = new BufferedReader(
    new InputStreamReader(process.getInputStream()))) {

    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

```

现在输出到文件。

```java
$ echo %cd%
C:\Users\Jano\Documents\tmp
$ more output.txt
Thu 02/14/2019

```

当前日期已写入`output.txt`文件。

## `ProcessBuilder`重定向输入和输出

下一个示例同时重定向输入和输出。

`src/resources/input.txt`

```java
sky
blue
steel
morning
coffee
earth
forest

```

这是`input.txt`文件的内容。

`ProcessBuilderRedirectIOEx.java`

```java
package com.zetcode;

import java.io.File;
import java.io.IOException;

public class ProcessBuilderRedirectIOEx {

    public static void main(String[] args) throws IOException {

        var processBuilder = new ProcessBuilder();

        processBuilder.command("cat")
                .redirectInput(new File("src/resources", "input.txt"))
                .redirectOutput(new File("src/resources/", "output.txt"))
                .start();
    }
}

```

在程序中，我们将输入从`input.txt`文件重定向到`cat`命令，并将命令的输出重定向到`output.txt`文件。

## `ProcessBuilder`继承 IO

`inheritIO()`将子流程标准 I/O 的源和目的地设置为与当前 Java 流程相同。

`ProcessBuilderInheritIOEx.java`

```java
package com.zetcode;

import java.io.IOException;

public class ProcessBuilderInheritIOEx {

    public static void main(String[] args) throws IOException, InterruptedException {

        var processBuilder = new ProcessBuilder();

        processBuilder.command("cmd.exe", "/c", "dir");

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

        int exitCode = process.waitFor();
        System.out.printf("Program ended with exitCode %d", exitCode);
    }
}

```

通过继承已执行命令的 IO，我们可以跳过读取步骤。 程序输出项目目录的内容和显示退出代码的消息。

```java
02/14/2019  04:55 PM    <DIR>          .
02/14/2019  04:55 PM    <DIR>          ..
02/19/2019  01:11 PM    <DIR>          .idea
02/14/2019  04:55 PM    <DIR>          out
02/14/2019  04:52 PM                     433 ProcessBuilderInheritIOEx.iml
02/14/2019  04:53 PM    <DIR>          src
                1 File(s)            433 bytes
                5 Dir(s)  157,350,264,832 bytes free
Program ended with exitCode 0

```

我们同时获得执行的命令和自己的 Java 程序的输出。

## `ProcessBuilder`环境

`environment()`方法返回流程构建器环境的字符串映射视图。

`ProcessBuilderEnvEx.java`

```java
package com.zetcode;

public class ProcessBuilderEnvEx {

    public static void main(String[] args) {

        var pb = new ProcessBuilder();
        var env = pb.environment();

        env.forEach((s, s2) -> {
            System.out.printf("%s %s %n", s, s2);
        });

        System.out.printf("%s %n", env.get("PATH"));
    }
}

```

该程序显示所有环境变量。

```java
configsetroot C:\WINDOWS\ConfigSetRoot 
USERDOMAIN_ROAMINGPROFILE LAPTOP-OBKOFV9J 
LOCALAPPDATA C:\Users\Jano\AppData\Local 
PROCESSOR_LEVEL 6 
USERDOMAIN LAPTOP-OBKOFV9J 
LOGONSERVER \\LAPTOP-OBKOFV9J 
JAVA_HOME C:\Users\Jano\AppData\Local\Programs\Java\openjdk-11\ 
SESSIONNAME Console 
...

```

这是 Windows 上的示例输出。

在下一个程序中，我们定义一个自定义环境变量。

`ProcessBuilderEnvEx2.java`

```java
package com.zetcode;

import java.io.IOException;

public class ProcessBuilderEnvEx2 {

    public static void main(String[] args) throws IOException {

        var pb = new ProcessBuilder();
        var env = pb.environment();

        env.put("mode", "development");

        pb.command("cmd.exe", "/c", "echo", "%mode%");

        pb.inheritIO().start();
    }
}

```

该程序定义一个`mode`变量并在 Windows 上输出。

```java
pb.command("cmd.exe", "/c", "echo", "%mode%");

```

`%mode%`是 Windows 的环境变量语法； 在 Linux 上，我们使用`$mode`。

## `ProcessBuilder`目录

`directory()`方法设置流程构建器的工作目录。

`ProcessBuilderDirectoryEx.java`

```java
package com.zetcode;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

public class ProcessBuilderDirectoryEx {

    public static void main(String[] args) throws IOException {

        var homeDir = System.getProperty("user.home");

        var pb = new ProcessBuilder();

        pb.command("cmd.exe", "/c", "dir");
        pb.directory(new File(homeDir));

        var process = pb.start();

        try (var reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {

            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}

```

该示例将主目录设置为流程生成器的当前目录。 我们显示主目录的内容。

```java
var homeDir = System.getProperty("user.home");

```

我们得到用户的主目录。

```java
pb.command("cmd.exe", "/c", "dir");

```

我们定义了一个在 Windows 上执行`dir`程序的命令。

```java
pb.directory(new File(homeDir));

```

我们设置流程构建器的目录。

```java
Volume in drive C is Windows
Volume Serial Number is 4415-13BB

Directory of C:\Users\Jano

02/14/2019  11:48 AM    <DIR>          .
02/14/2019  11:48 AM    <DIR>          ..
10/13/2018  08:38 AM    <DIR>          .android
01/31/2019  10:58 PM               281 .bash_history
12/17/2018  03:02 PM    <DIR>          .config
...

```

这是一个示例输出。

## `ProcessBuilder`非阻塞操作

在下面的示例中，我们创建一个异步过程。

`ProcessBuilderNonBlockingEx.java`

```java
package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

public class ProcessBuilderNonBlockingEx {

    public static void main(String[] args) throws InterruptedException,
            ExecutionException, TimeoutException, IOException {

        var executor = Executors.newSingleThreadExecutor();

        var processBuilder = new ProcessBuilder();

        processBuilder.command("cmd.exe", "/c", "ping -n 3 google.com");

        try {

            var process = processBuilder.start();

            System.out.println("processing ping command ...");
            var task = new ProcessTask(process.getInputStream());
            Future<List<String>> future = executor.submit(task);

            // non-blocking, doing other tasks
            System.out.println("doing task1 ...");
            System.out.println("doing task2 ...");

            var results = future.get(5, TimeUnit.SECONDS);

            for (String res : results) {
                System.out.println(res);
            }

        } finally {
            executor.shutdown();
        }
    }

    private static class ProcessTask implements Callable<List<String>> {

        private InputStream inputStream;

        public ProcessTask(InputStream inputStream) {
            this.inputStream = inputStream;
        }

        @Override
        public List<String> call() {
            return new BufferedReader(new InputStreamReader(inputStream))
                    .lines()
                    .collect(Collectors.toList());
        }
    }
}

```

该程序创建一个在控制台上运行`ping`命令的进程。 它在`Executors.newSingleThreadExecutor()`方法的帮助下在单独的线程中执行。

```java
processing ping command ...
doing task1 ...
doing task2 ...

Pinging google.com [2a00:1450:4001:825::200e] with 32 bytes of data:
Reply from 2a00:1450:4001:825::200e: time=108ms 
Reply from 2a00:1450:4001:825::200e: time=111ms 
Reply from 2a00:1450:4001:825::200e: time=112ms 

Ping statistics for 2a00:1450:4001:825::200e:
    Packets: Sent = 3, Received = 3, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 108ms, Maximum = 112ms, Average = 110ms

```

这是输出。

## `ProcessBuilder`管道操作

管道是一种用于将信息从一个程序进程传递到另一个程序进程的技术。

`ProcessBuilderPipeEx.java`

```java
package com.zetcode;

import java.io.File;
import java.io.IOException;

public class ProcessBuilderPipeEx {

    public static void main(String[] args) throws IOException {

        var homeDir = System.getProperty("user.home");

        var processBuilder = new ProcessBuilder();

        processBuilder.command("cmd.exe", "/c", "dir | grep [dD]o");

        processBuilder.directory(new File(homeDir));
        processBuilder.inheritIO().start();
    }
}

```

该示例通过管道（|）将信息从`dir`命令发送到`grep`命令。

```java
Volume in drive C is Windows
11/14/2018  06:57 PM    <DIR>          .dotnet
02/18/2019  10:54 PM    <DIR>          Documents
02/17/2019  01:11 AM    <DIR>          Downloads

```

这是输出。

在本教程中，我们使用 Java 的`ProcessBuilder`执行 OS 进程。 您可能也对相关教程感兴趣： [Java `Files.walk`教程](/java/fileswalk/)和 [Java 教程](/lang/java/)。