目录
- 一 简介
- 二 问题及解决方法
- 问题1 需要等待问题
- 原因
- 解决方法
- 问题2 卡死
- 原因
- 解决方法
- 问题3 命令返回值偶发不准
- 原因
- 解决方法
- 三 最终工具类完整代码
一 简介
在工作中需要java程序运行一些shell命令,可以使用java的Runtime来执行。该类主要提供以下方法来完成命令执行
Process exec(String command)
Process exec(String command, String[] envp)
Process exec(String command, String[] envp, File dir)Process exec(String cmdarray[])
Process exec(String[] cmdarray, String[] envp)
Process exec(String[] cmdarray, String[] envp, File dir)
exec 的方法说明中有几个要点可关注,以下问题也是和这几点相关
1.Executes the specified string command in a separate process.
在分开的(新的)进程执行命令
2、return an instance of a subclass of Process that can be used to control the process and obtain information about it
返回的Process ,是返回一个子进程
3、the created subprocess does not have its own terminal or console. All its standard I/O (i.e. stdin, stdout, stderr) operations will be redirected to the parent process, where they can be accessed via the streams obtained using the methods getOutputStream(), getInputStream(), and getErrorStream(). The parent process uses these streams to feed input to and get output from the subprocess. Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, or even deadlock.
创建的新的进程没有终端窗口的话,标准I/O会重定向到父进程中,如果缓冲区有限制的话,会导致锁或死锁(子进程写不进去,父进程一直等待子进程执行)。
其中返回 Process 的java文档说明如下
二 问题及解决方法
问题1 需要等待问题
int success = process.exitValue();// 0成功 1失败
没有得到预期结果,执行命令失败
原因
从简介中可以看到,该方法是启动新的进程来异步执行命令,可能新进程还没有结束,主进程已经继续往下执行了。
解决方法
添加等待进程执行完毕之后,继续执行
boolean wait = process.waitFor(2, TimeUnit.MINUTES);
问题2 卡死
有些命令正常,但当执行后有大量信息输出时,卡死。
原因
新进程执行命令时产生大量错误流或者标准流,由于子进程没有终端,会把流重定向到父进程中,当缓冲区较小时,会写入失败,父进程还在一直等待子进程结束才能继续,因此死锁了。
解决方法
启动新的线程来读取标准I/O.
new Thread(() -> {
BufferedReader b = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = "";
try {
while ((line = b.readLine()) != null) {
sb.append(line).append("\n");
}
b.close();
log.info("新线程获取输入流结束");
} catch (Exception ex) {
log.error("获取输入流异常", ex);
} finally {
latch.countDown();// 执行完毕后 减掉数量
}
}).start();
// 新启动线程 错误流直接输出出去 以免阻塞缓冲区
new Thread(() -> {
BufferedReader b = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line = "";
try {
while ((line = b.readLine()) != null) {
log.error("错误流输出:" + line);
}
} catch (Exception ex) {
log.error("获取错误流异常", ex);
}
}).start();
问题3 命令返回值偶发不准
获取的标注输出内容可能不全。
原因
按问题2的解决方式,获取标准流采用新线程,但这时是异步处理,当下边主进程获取流的内容时,有可能新的线程还没执行完
解决方法
采用门闩方式处理。预期的线程全部执行完,才能进行后续操作。
三 最终工具类完整代码
public class ExecCmdUtil {
private ExecCmdUtil() {
}
public static AIPRet<String> exec(String cmd) {
String result;
StringBuilder sb = new StringBuilder();
log.info("即将执行命令:" + cmd);
try {
Process process = Runtime.getRuntime().exec(cmd);
final CountDownLatch latch = new CountDownLatch(1);// 来个门闩,当获取输入流进程结束时才可以继续执行后续步骤
// 启动新线程 获取标准输入流
new Thread(() -> {
BufferedReader b = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = "";
try {
while ((line = b.readLine()) != null) {
sb.append(line).append("\n");
}
b.close();
log.info("新线程获取输入流结束");
} catch (Exception ex) {
log.error("获取输入流异常", ex);
} finally {
latch.countDown();// 执行完毕后 减掉数量
}
}).start();
// 新启动线程 错误流直接输出出去 以免阻塞缓冲区
new Thread(() -> {
BufferedReader b = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line = "";
try {
while ((line = b.readLine()) != null) {
log.error("错误流输出:" + line);
}
} catch (Exception ex) {
log.error("获取错误流异常", ex);
}
}).start();
boolean await = latch.await(2, TimeUnit.MINUTES);// 等待输入流获取完成,所有门闩就执行完
log.info("异步线程门闩等待结果latch.await:" + await);
boolean wait = process.waitFor(2, TimeUnit.MINUTES);
log.info("处理等待结果waitFor:" + wait);
int success = process.exitValue();// 0成功 1失败
log.info("处理退出结果exitValue:" + success);
if (sb.length() > 0 && sb.lastIndexOf("\n") > 0) {
result = sb.substring(0, sb.lastIndexOf("\n"));
} else {
result = sb.toString();
}
log.info("命令:" + cmd + "执行完毕,返回信息:" + result);
if (0 == success) {
return AIPRet.<String>builder()
.success(true)
.data(result)
.build();
} else {
return AIPRet.<String>builder()
.success(false)
.message(result)
.build();
}
} catch (InterruptedException ex) {
log.error("执行命令异常:", ex);
Thread.currentThread().interrupt();
return AIPRet.<String>builder()
.success(false)
.message("执行命令" + cmd + "失败")
.build();
} catch (Exception ex) {
log.error("执行命令异常", ex);
return AIPRet.<String>builder()
.success(false)
.message("执行命令" + cmd + "失败")
.build();
}
}
public static AIPRet<String> execute(String[] cmdArray) {
Runtime r = Runtime.getRuntime();
String result = "";
StringBuilder sb = new StringBuilder();
try {
log.info("======即将执行命令: " + Arrays.toString(cmdArray) + "=======");
Process p = r.exec(cmdArray);
final CountDownLatch latch = new CountDownLatch(1);// 来个门闩,当获取输入流进程结束时才可以继续执行后续步骤
new Thread(() -> {
BufferedReader b = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = "";
try {
while ((line = b.readLine()) != null) {
sb.append(line).append("\n");
}
b.close();
log.info("新线程获取输入流结束");
} catch (Exception ex) {
log.error("获取输入流异常", ex);
} finally {
latch.countDown();// 执行完毕后 减掉数量
}
}).start();
new Thread(() -> {
BufferedReader b = new BufferedReader(new InputStreamReader(p.getErrorStream()));
String line = "";
try {
while ((line = b.readLine()) != null) {
log.error("错误流输出:" + line);
}
} catch (Exception ex) {
log.error("获取错误流异常", ex);
}
}).start();
boolean await = latch.await(2, TimeUnit.MINUTES);// 等待输入流获取完成,所有门闩就执行完
log.info("异步线程门闩等待结果latch.await:" + await);
boolean wait = p.waitFor(2, TimeUnit.MINUTES);
log.info("执行等待结果waitFor:" + wait);
int success = p.exitValue();// 0成功 1失败
log.info("======执行命令结果exitValue:" + success + "==========");
if (sb.length() > 0 && sb.lastIndexOf("\n") > 0) {
result = sb.substring(0, sb.lastIndexOf("\n"));
} else {
result = sb.toString();
}
log.info("命令:" + Arrays.toString(cmdArray) + "执行完毕,返回信息:" + result);
if (success != 0) {
log.info("======执行命令返回信息:" + result + "==========");
return AIPRet.<String>builder()
.success(false)
.message("执行命令" + Arrays.toString(cmdArray) + "失败" + result)
.build();
} else {
return AIPRet.<String>builder()
.success(true)
.data(result)
.build();
}
} catch (Exception ex) {
log.error("执行命令异常:" + Arrays.toString(cmdArray), ex);
Thread.currentThread().interrupt();
return AIPRet.<String>builder()
.success(false)
.message("执行命令" + Arrays.toString(cmdArray) + "失败")
.build();
}
}
}