Java执行命令行

1. 概述

在日常开发过程中,我们经常会需要调用系统的命令行,比如执行一些系统工具/库来压缩(变换)图片等。本文将介绍从Java代码执行shell命令的两种方法。

第一种是使用Runtime类并调用其exec方法。

第二种更可定制的方法是创建和使用ProcessBuilder实例。

2. 操作系统依赖性

在我们要创建执行shell命令的新Process之前,我们需要首先确定运行JVM的操作系统。

这是因为,在Windows上,我们需要将命令作为参数运行到cmd.exe shell,在所有其他操作系统上我们可以发出一个名为sh的标准shell

1
2
boolean isWindows = System.getProperty("os.name")
.toLowerCase().startsWith("windows");

3. 输入和输出

此外, 我们需要一种方法来 Hook 住输入和输出流。

至少必须消费掉输出 - 否则我们的进程不会成功返回,而是会挂起。

让我们实现一个名为StreamGobbler的常用类,它使用一个InputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static class StreamGobbler implements Runnable {
private InputStream inputStream;
private Consumer<String> consumer;

public StreamGobbler(InputStream inputStream, Consumer<String> consumer) {
this.inputStream = inputStream;
this.consumer = consumer;
}

@Override
public void run() {
new BufferedReader(new InputStreamReader(inputStream)).lines()
.forEach(consumer);
}
}

注意:此类实现了Runnable接口,这意味着它可以由任何Executor执行。

4. Runtime.exec()

Runtime.exec()的方法调用是一种简单的,尚未定制的生成新子进程的方法。

在下面的示例中,我们将请求用户主目录的目录列表并将其打印到控制台:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String homeDirectory = System.getProperty("user.home");
Process process;
if (isWindows) {
process = Runtime.getRuntime()
.exec(String.format("cmd.exe /c dir %s", homeDirectory));
} else {
process = Runtime.getRuntime()
.exec(String.format("sh -c ls %s", homeDirectory));
}
StreamGobbler streamGobbler =
new StreamGobbler(process.getInputStream(), System.out::println);
Executors.newSingleThreadExecutor().submit(streamGobbler);
int exitCode = process.waitFor();
assert exitCode == 0;

5. ProcessBuilder

对于计算问题的第二个实现,我们将使用ProcessBuilder。这比Runtime方法更受欢迎,因为我们可以自定义一些细节。

例如,我们能够:

  • 使用builder.directory()更改我们的shell命令正在运行的工作目录
  • 使用builder.environment()将自定义键值映射设置为环境
  • 将自定义输入和输出流重定向
  • 使用builder.inheritIO()将它们都继承到当前JVM进程的流
1
2
3
4
5
6
7
8
9
10
11
12
13
ProcessBuilder builder = new ProcessBuilder();
if (isWindows) {
builder.command("cmd.exe", "/c", "dir");
} else {
builder.command("sh", "-c", "ls");
}
builder.directory(new File(System.getProperty("user.home")));
Process process = builder.start();
StreamGobbler streamGobbler =
new StreamGobbler(process.getInputStream(), System.out::println);
Executors.newSingleThreadExecutor().submit(streamGobbler);
int exitCode = process.waitFor();
assert exitCode == 0;

六,结论

正如我们在本快速教程中看到的,我们可以用两种不同的方式在Java中执行shell命令。

通常,如果您计划自定义生成进程的执行,例如更改其工作目录,则应考虑使用ProcessBuilder