ZetCode

Java ProcessBuilder

最后修改时间 2025 年 5 月 1 日

在本文中,我们将展示如何使用 ProcessBuilder 创建操作系统进程。

Java 中的 ProcessBuilder 类用于创建和管理操作系统进程。它提供了一种便捷的方式来执行系统命令、管理进程环境以及控制输入/输出流。

start 方法创建一个新的 Process 实例,具有以下可配置的属性

运行程序

使用 command 执行程序。 使用 waitFor 我们可以等待进程完成。

Main.java
import java.io.IOException;

void main() 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 记事本应用程序。 它返回其退出代码。

命令输出

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

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

void main() throws IOException {

    var processBuilder = new ProcessBuilder();
    processBuilder.command("cal", "2022", "-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 命令。

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

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

var process = processBuilder.start();

该进程通过 start 启动。

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

通过 getInputStream 方法,我们可以从进程的标准输出获取输入流。

$ java Main.java
    Február 2022
Ne Po Ut St Št Pi So
       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

重定向输出

使用 redirectOutput,我们可以重定向进程构建器的标准输出目标。

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

void main() throws IOException {

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

    processBuilder.command("date");

    var fileName = new File(String.format("%s/Documents/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 命令。

processBuilder.redirectOutput(fileName);

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

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

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

现在输出转到文件。

$ java Main.java
$ cat ~/Documents/output.txt 
Tue, Feb 20, 2024 10:18:11 PM

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

重定向输入和输出

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

input.txt
sky
blue
steel
morning
coffee
earth
forest

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

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

void main() throws IOException {

    var processBuilder = new ProcessBuilder();

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

在该程序中,我们将来自 input.txt 文件的输入重定向到 cat 命令,并将该命令的输出重定向到 output.txt 文件。

inheritIO 方法

inheritIO 将子进程标准 I/O 的源和目标设置为与当前 Java 进程的相同。

Main.java
import java.io.IOException;

void main() 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 Main.java
Directory of C:\Users\Jano\Documents\prog\java\simple

20. 02. 2024  22:25    <DIR>          .
20. 02. 2024  21:45    <DIR>          ..
20. 02. 2024  22:23               357 Main.java
                1 File(s)            357 bytes
                2 Dir(s)  34 407 276 544 bytes free
Program ended with exitCode 0

我们同时获得了已执行命令的输出以及我们自己的 Java 程序的输出。

environment 方法

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

Main.java
void main() {

    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 Main.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\.jdks\jdk21.0.2_13
SESSIONNAME Console
...

这是 Windows 上的示例输出。

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

Main.java
import java.io.IOException;

void main() 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 上输出它。

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

%mode% 是 Windows 环境变量的语法;在 Linux 上我们使用 $mode

directory 方法

directory 方法设置进程构建器的工作目录。

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

void main() 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);
        }
    }
}

该示例将主目录设置为进程构建器的当前目录。 我们显示主目录的内容。

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

我们获取用户的主目录。

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

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

pb.directory(new File(homeDir));

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

$ java Main.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
...

非阻塞操作

在以下示例中,我们创建一个异步进程。

Main.java
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;

void main() throws InterruptedException,
        ExecutionException, TimeoutException, IOException {

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

class ProcessTask implements Callable<List<String>> {

    private final 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 Main.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

管道操作

管道是一种机制,允许数据从一个进程流向另一个进程,从而使程序能够无缝地交换信息。

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

void main() 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 Main.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 - 语言参考

在本文中,我们使用了 Java 的 ProcessBuilder 来执行 OS 进程。

作者

我的名字是 Jan Bodnar,我是一位充满激情的程序员,拥有丰富的编程经验。 我自 2007 年以来一直在撰写编程文章。 迄今为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有Java教程