在windows下,java调用cmd需要防止阻塞。而且有三种场景:
1.直接执行单句的cmd命令。
2.执行命令集.bat文件(这种跟情况1其实一样,写好.bat文件即可)。
3.在一个会话中交互式执行多句命令(当然这个做不到真正交互,只是可以模拟一句一句输入)。
直接给结果:
package xxx
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.thread.ThreadFactoryBuilder;
import cn.hutool.core.util.StrUtil;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static lombok.AccessLevel.PRIVATE;
/**
* windows/Linux下执行命令的工具
*
* @author xxx
* @since 0000/00/00
**/
@Slf4j
@NoArgsConstructor(access = PRIVATE)
public class CommandExecutionUtil {
/** 设定线程名称格式 */
private static ThreadFactory factory = new ThreadFactoryBuilder().setNamePrefix("read echo task-").build();
/** 执行程序需要同时读取掉两个回显流(执行结果和错误结果) */
private static ThreadPoolExecutor executor
= new ThreadPoolExecutor(6, 60, 60, TimeUnit.SECONDS, new SynchronousQueue<>(), factory);
/**
* 直接执行(通过bash来执行命令 比如./xxxxx.sh)
*
* @param command sh文件+参数,空格分开
* @param timeoutSeconds 等待超时时间
* @return 执行结果
*/
public static List<String> executeLinuxSh(String command, long timeoutSeconds) {
log.info("the execute command is: {}", command);
if (StrUtil.isEmpty(command)) {
log.warn("the execute command is empty.");
return Collections.emptyList();
}
Process process = null;
List<String> outInfos = Collections.emptyList();
try {
process = Runtime.getRuntime().exec(command);
outInfos = getEchoInfos(process.getInputStream(), process.getErrorStream(), timeoutSeconds);
} catch (Exception e) {
log.error("System execute command failed!", e);
} finally {
if (Objects.nonNull(process)) {
process.destroy();
}
}
return outInfos;
}
/**
* linux场景执行命令(通过sh来执行 sh xxxxx.sh形式)
*
* @param command command 执行的命令
* @param timeoutSeconds 等待超时时间
* @return 执行结果
*/
public static List<String> executeLinuxCommandWaitTimeout(String command, long timeoutSeconds) {
if (StrUtil.isEmpty(command)) {
log.warn("the execute command is empty.");
return Collections.emptyList();
}
String[] cmdArr = {"/bin/sh", "-c", command};
Process process = null;
List<String> outInfos = Collections.emptyList();
try {
process = Runtime.getRuntime().exec(cmdArr);
outInfos = getEchoInfos(process.getInputStream(), process.getErrorStream(), timeoutSeconds);
} catch (Exception e) {
log.error("System execute command failed!", e);
} finally {
if (Objects.nonNull(process)) {
process.destroy();
}
}
return outInfos;
}
/**
* 执行命令附加超时时间
*
* @param command 命令
* @param timeoutSeconds 超时时间
* @return 执行结果
*/
public static List<String> executeWinCommandWaitTimeout(String command, long timeoutSeconds) {
if (StrUtil.isEmpty(command)) {
log.warn("the execute command is empty.");
return Collections.emptyList();
}
try {
return executeCommand("cmd /c " + command, timeoutSeconds);
} catch (Exception e) {
log.error("System execute command failed!", e);
}
return Collections.emptyList();
}
/**
* 在windows场景下,执行单个cmd命令
*
* @param command windows场景单行命令
* @return 执行结果文本
* @throws IOException IO异常
* @throws ExecutionException 执行异常
* @throws InterruptedException 记时异常
* @throws TimeoutException 超时异常
*/
private static List<String> executeCommand(String command, long timeoutSeconds)
throws IOException, ExecutionException,
InterruptedException, TimeoutException {
Process process = Runtime.getRuntime().exec(command);
// 命令执行错误回显流、命令执行的回显流
List<String> outInfos = getEchoInfos(process.getInputStream(), process.getErrorStream(), timeoutSeconds);
process.destroy();
return outInfos;
}
/**
* 执行windows的多个cmd命令(类似脚本执行)
*
* @param commands 多个cmd命令
* @return 执行结果
*/
public static List<String> executeWinCommands(List<String> commands) {
if (CollectionUtil.isEmpty(commands)) {
log.warn("the execute commands is empty.");
return Collections.emptyList();
}
try {
return executeCommands(commands);
} catch (Exception e) {
log.error("System execute commands failed!", e);
}
return Collections.emptyList();
}
/**
* 在不能使用.bat(windows场景)脚本的场景下,可以使用此方法,执行脚本(脚本语句拆分到list里面)
*
* @param commands 脚本语句,list里面一句相当于一行
* @return 执行后的交互结果文本
* @throws IOException IO异常
* @throws ExecutionException 执行异常
* @throws InterruptedException 记时异常
* @throws TimeoutException 超时异常
*/
private static List<String> executeCommands(List<String> commands) throws IOException, ExecutionException,
InterruptedException, TimeoutException {
Process process = Runtime.getRuntime().exec("cmd");
// 同一个会话命令持续输入流
try (OutputStream processIn = process.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(processIn);
BufferedWriter bw = new BufferedWriter(osw)) {
for (String command : commands) {
bw.write(command);
bw.newLine();
}
bw.flush();
}
// 命令执行的回显流、命令执行错误回显流
List<String> outInfos = getEchoInfos(process.getInputStream(), process.getErrorStream(), 30);
process.destroy();
return outInfos;
}
private static List<String> getEchoInfos(InputStream processOut, InputStream processErr, long timeoutSeconds) throws ExecutionException,
InterruptedException, TimeoutException {
Future<List<String>> outFuture = executor.submit(() -> readEcho(processOut));
Future<List<String>> errFuture = executor.submit(() -> readEcho(processErr));
List<String> outInfos = outFuture.get(timeoutSeconds, TimeUnit.SECONDS);
List<String> errInfos = errFuture.get(timeoutSeconds, TimeUnit.SECONDS);
if (CollectionUtil.isNotEmpty(errInfos)) {
log.warn("execute commands error: {}", errInfos);
}
return outInfos;
}
private static List<String> readEcho(InputStream echoStream) throws IOException {
try (InputStream is = echoStream;
InputStreamReader isr = new InputStreamReader(is, ApplicationContext.isWindows() ? Charset.forName("GBK") : StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr)) {
List<String> echoInfos = new ArrayList<>();
String echoInfo;
while ((echoInfo = br.readLine()) != null) {
echoInfos.add(echoInfo);
}
return echoInfos;
}
}
}
还有一种特殊的场景,将java程序注册成windows的SYSTEM权限的服务时,调用cmd命令是有权限问题的,比如想启动一个exe,执行start xxx.exe只能启动一个没有窗口的后台,因为SYSTEM权限不会调用到窗口,没有界面。
这种情况,需要用C/C++写一个通过管理员权限启动一个进程的函数。打包dll文件,java通过jna方式调用此dll,并传入要执行的命令。