processBuilder
复制 ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("ping", "-c", "5", "www.baidu.com");
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
// 向子进程写入数据
try (OutputStream outputStream = process.getOutputStream()) {
outputStream.write("Hello, world!\n".getBytes());
outputStream.flush();
}
try (InputStream inputStream = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
int exitCode = process.waitFor();
System.out.println("Exit code: " + exitCode);
工作目录
使用directory
方法可以修改子进程默认的工作目录。默认情况下,当前工作目录设置为user.dir
系统属性返回的值。
复制 ProcessBuilder processBuilder = new ProcessBuilder(commandList);
processBuilder.directory(new File("/home/var"));
环境变量
它使用System.getenv()
但作为 Map 返回当前进程环境的副本。下面的示例中,演示了如何获取当前环境变量,以及修改环境变量并传入子进程中。
复制 ProcessBuilder processBuilder = new ProcessBuilder(commandList);
Map<String, String> environment = processBuilder.environment();
environment.forEach((k, v) -> System.out.println(k + "=" + v));
复制 ProcessBuilder processBuilder = new ProcessBuilder(commandList);
Map<String, String> environment = processBuilder.environment();
environment.put("k", "v");
重定向输入和输出
重定向输入
复制 var processBuilder = new ProcessBuilder();
processBuilder.command("cat")
.redirectInput(new File("src/main/resources", "input.txt"))
.redirectOutput(new File("src/main/resources/", "output.txt"))
.start();
在程序中,我们将输入从 input.txt
文件重定向到 cat
命令,并将命令的输出重定向到 output.txt
文件
重定向输出
复制 ProcessBuilder processBuilder = new ProcessBuilder(commandList);
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT); // 将标准输出重定向到父进程
processBuilder.redirectOutput(ProcessBuilder.Redirect.DISCARD); // 将标准输出重定向到标准输出
processBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE); // 将标准输出重定向到管道
processBuilder.redirectOutput(new File("./output.log")); // 将标准输出重定向到文件
processBuilder.redirectOutput(ProcessBuilder.Redirect.appendTo(new File("./output.txt"))); // 将标准输出重定向到文件
可以指定 INFO
和 ERROR
日志输出到不同的文件。
复制 // INFO 日志
processBuilder.redirectOutput(new File("./output.log"));
// ERROR 日志
processBuilder.redirectError(new File("./error.log"));
继承IO
这个示例中,将看到 inheritIO()
方法的作用。当我们想将子进程的 I/O 重定向到当前进程的标准 I/O 时,可以使用这个方法:
复制 ProcessBuilder processBuilder = new ProcessBuilder(commandList);
processBuilder.redirectErrorStream(true);
// 把子线程 I/O 输出重定向当前进程
processBuilder.inheritIO();
Process process = processBuilder.start();
管道操作
从 Java9 开始,ProcessBuilder
引入了管道的概念,可以把一个进程的输出作为另外一个进程的输入再次操作。
复制 public static List<Process> startPipeline(List<ProcessBuilder> builders)
使用这个方法我们进行例如这样的操作:ls -l | wc -l
,列出文件目录,然后统计行数。
复制 ProcessBuilder lsProcessBuilder = new ProcessBuilder("/bin/bash", "-c", "ls -l");
ProcessBuilder wcProcessBuilder = new ProcessBuilder("wc", "-l");
List<Process> processes = ProcessBuilder.startPipeline(Arrays.asList(lsProcessBuilder, wcProcessBuilder));
Process process = processes.getLast();
System.out.println("pid:" + process.pid());
System.out.println("exitCode:" + process.waitFor());
超时与终止
如果某个进程在指定的时间段内没有结束,那么可以认为该进程超时了,我们可以通过 destroyForcibly
来杀死该进程。
复制 List<String> commandList = List.of("bash", "-c", "for i in {1..10}; do if (( $RANDOM % 2 )); then echo \"这是标准输出信息 $i\"; else >&2 echo \"这是标准错误信息 $i\"; fi; done");
ProcessBuilder processBuilder = new ProcessBuilder(commandList);
Process process = processBuilder.start();
// 等待一定的时间
boolean waitFor = process.waitFor(3, TimeUnit.SECONDS);
// 若未退出,杀死子进程
if (!waitFor) {
process.destroyForcibly(); // 用于杀死子进程
process.waitFor();
System.out.println("杀死子进程");
}
异步处理
在很多情况下,执行一个命令启动一个新线程后,我们不想阻塞等待进程完成,想要异步华,在进程执行完成后进行通知回调。这时可以使用 CompletableFuture
来实现这个功能。
复制 ProcessBuilder processBuilder = new ProcessBuilder("ping", "-c", "10", "www.baidu.com");
processBuilder.inheritIO(); // 把子线程 I/O 输出重定向到父线程
CompletableFuture.supplyAsync(() -> {
Process process = null;
try {
process = processBuilder.start();
process.waitFor();
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
return null;
}).thenAccept(result -> {
System.out.println("进程执行结束");
});
Thread.sleep(14 * 1000);