Content Table

Java 执行命令

使用 Java 执行命令,可以:

下面以执行 ls -l / 为例演示相关代码。

ProcessBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void executeCommandUsingProcessBuilder() throws Exception {
ProcessBuilder pb = new ProcessBuilder("ls", "-l", "/");
Process p = pb.start();

StringBuilder sbOk = new StringBuilder();
StringBuilder sbError = new StringBuilder();

// 读取正常输出
BufferedReader okReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String okLine = null;
while ((okLine = okReader.readLine()) != null) {
sbOk.append(okLine).append("\n");
}

// 读取错误输出
BufferedReader errorReader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
String errorLine = null;
while ((errorLine = errorReader.readLine()) != null) {
sbError.append(errorLine).append("\n");
}

System.out.println(sbOk.toString());
System.out.println(sbError.toString());
}

疑问: 虽然如上能正常的获取到进程的正常和错误输出,慎用,因为缓冲区写满了的时候,由于没有读取其中的数据,无法继续写入数据,导致线程阻塞,对外现象就是进程无法停止,也不占资源,什么反应也没有,参考使用 JDK 写法

Apache Commons Exec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void executeCommandUsingCommonsExec() throws Exception {
CommandLine cmdLine = CommandLine.parse("ls -l /");
DefaultExecutor executor = new DefaultExecutor();
executor.setExitValues(null);

ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);
executor.setWatchdog(watchdog);

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream, errorStream);

executor.setStreamHandler(streamHandler);
executor.execute(cmdLine);

// 获取程序外部程序执行结果
String out = outputStream.toString("UTF-8");
String error = errorStream.toString("UTF-8");

System.out.println(out);
System.out.println(error);
}

提示: 内部是使用多线程的方式获取正常和错误输出,解决了 Runtime 缓冲区问题导致的线程卡死。此外 Exec 还提供了 Watchdog 的功能,超时会主动杀掉进程,异步执行任务,使用一个完整的字符串方式传入要执行的命令方便不少 (有时不推荐使用) 等,推荐使用 Exec 执行命令

程序结束的时候调用 Execute.isFailure(exitvalue) && watchdog.killedProcess() 查看程序是正常结束还是被杀掉的。

The code was ported from Apache Ant and extensively tested on various platforms. So there is no reason not to use it and it is very likely better than any home-grown library.

复合命令

把上面代码中的 ls -l / 修改为 ls -l / | grep '^d' 再次执行,2 个程序都报错。

1
2
3
ls: '^d': No such file or directory
ls: grep: No such file or directory
ls: |: No such file or directory

但这条命令在终端里执行是没问题的呀?其实这理论上来说不是一个命令,而是一个复合命令,使用管道把 2 条命令连在一起使用,再如命令中有输入输出重定向等在终端里能执行成功,上面的程序都会报错。

在实际使用的过程中很多时候就是要执行复合或者复杂命令,而不是简单的命令,例如访问 MongoDB:

1
/usr/local/bin/mongo 192.168.12.20:34005/test --authenticationDatabase admin -u admin -p admin --quiet < /temp/test.js

为了解决这个问题,我们可以把要执行的命令写入临时脚本文件,执行这个脚本,最后删除此临时脚本文件。

Windows 写入 bat 脚本,Linux 和 Mac 写入 shell 脚本。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public static void executeCommandUsingCommonsExec() throws Exception {
String command = "ls -l / | grep '^d'";

// 把命令保存到临时脚本文件
Path shPath = Files.createTempFile("aloha-", ".sh");
Files.write(shPath, command.getBytes(StandardCharsets.UTF_8));

try {
// 执行脚本文件
String result = executeScript(shPath.toString());
System.out.println(result);
} finally {
// 删除临时脚本文件
Files.delete(shPath);
}
}

/**
* 执行脚本文件
*
* @param path 脚本文件的路径
* @return 返回脚本执行的结果
* @throws IOException 程序执行错误时抛出 IOException
*/
public static String executeScript(String path) throws IOException {
CommandLine cmdLine = CommandLine.parse("sh " + path);
DefaultExecutor executor = new DefaultExecutor();
executor.setExitValues(null);

ExecuteWatchdog watchdog = new ExecuteWatchdog(60000);
executor.setWatchdog(watchdog);

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream, errorStream);

executor.setStreamHandler(streamHandler);
executor.execute(cmdLine);

// 获取程序外部程序执行结果
String out = outputStream.toString("UTF-8");
String error = errorStream.toString("UTF-8");

return out + error;
}