执行系统命令

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")));  // 将标准输出重定向到文件

可以指定 INFOERROR 日志输出到不同的文件。

// 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);

最后更新于

这有帮助吗?