Java ProcessBuilder
最后修改时间 2025 年 5 月 1 日
在本文中,我们将展示如何使用 ProcessBuilder 创建操作系统进程。
Java 中的 ProcessBuilder 类用于创建和管理操作系统进程。它提供了一种便捷的方式来执行系统命令、管理进程环境以及控制输入/输出流。
start 方法创建一个新的 Process 实例,具有以下可配置的属性
- 命令 (Command) - 要运行的系统命令或可执行文件。
- 环境 (Environment) - 进程的一组环境变量。
- 工作目录 (Working directory) - 进程执行的目录。
- 输入源 (Source of input) - 指定进程的输入数据。
- 标准输出和错误输出的目标 (Destination for standard output and error output) - 控制进程将其输出和错误日志写入的位置。
- RedirectErrorStream - 将标准错误输出合并到标准输出中,以便进行简化的处理。
运行程序
使用 command 执行程序。 使用 waitFor 我们可以等待进程完成。
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 记事本应用程序。 它返回其退出代码。
命令输出
以下示例执行一个命令并显示其输出。
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,我们可以重定向进程构建器的标准输出目标。
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 文件。
重定向输入和输出
下一个示例同时重定向输入和输出。
sky blue steel morning coffee earth forest
这些是 input.txt 文件的内容。
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 进程的相同。
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 方法返回进程构建器环境的字符串映射视图。
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 上的示例输出。
在下一个程序中,我们定义一个自定义环境变量。
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 方法设置进程构建器的工作目录。
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 ...
非阻塞操作
在以下示例中,我们创建一个异步进程。
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
管道操作
管道是一种机制,允许数据从一个进程流向另一个进程,从而使程序能够无缝地交换信息。
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 来执行 OS 进程。
作者
列出所有Java教程。