Java可以通过Runtime.getRuntime().exec()方法调用linux平台下的命令及Shell脚本。
获取命令执行结果通常有两种,一种是waitfor方法,另一种是exitValue。
但waitfor方法可能造成阻塞,原因如下:
当调用exec方法后,JVM启动一个子进程,该进程会与JVM进程建立3个管道连接,即标准输入流、标准输出流、错误错误流。假设该程序不间断向标准输出流和标准错误流写数据,而JVM不读取,那么数据会暂存在Linux缓冲区中,缓冲区写满后该程序将无法继续写入,程序就会一直阻塞在waitfor方法,永远无法结束。
解决方法就是增加两个线程,一个负责读取标准输出流,一个负责读取标准错误流,这样数据就不会积压在缓冲区,waitfor方法可以正常结束。
总之,调用外部程序时需要注意以下两点:
1、如果外部程序有大量输出,需要有单独线程读取输出流和错误流
2、必须关闭3个句柄——标准输入流、标准输出流、标准错误流
考虑到阻塞问题以及为了获取命令输出,文中使用了exitValue方法。
代码如下
ShellUtils:执行外部命令的工具类
package com.wll.shell;
import com.wll.utils.CommonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class ShellUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(ShellUtils.class);
private static final long THREAD_SLEEP_TIME = 10;
private static final int DEFAULT_WAIT_TIME = 20 * 60 * 1000;
public static void runShell(String cmd) {
String[] command = new String[]{"/bin/sh", "-c", cmd};
try {
Process process = Runtime.getRuntime().exec(command);
ShellResult result = getProcessResult(process, DEFAULT_WAIT_TIME);
LOGGER.info("Command [{}] executed successfully.", cmd);
LOGGER.info(result.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取命令执行结果
* @param process 子进程
* @param waitTime 指定超时时间
* @return 命令执行输出结果
*/
public static ShellResult getProcessResult(Process process, long waitTime) {
ShellResult cmdResult = new ShellResult();
boolean isTimeout = false;
long loopNumber = waitTime / THREAD_SLEEP_TIME;
long realLoopNumber = 0;
int exitValue = -1;
StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream());
StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream());
errorGobbler.start();
outputGobbler.start();
try {
while (true) {
try {
Thread.sleep(THREAD_SLEEP_TIME);
exitValue = process.exitValue();
break;
} catch (InterruptedException e) {
realLoopNumber++;
if (realLoopNumber >= loopNumber) {
isTimeout = true;
break;
}
}
}
errorGobbler.join();
outputGobbler.join();
if (isTimeout) {
cmdResult.setErrorCode(ShellResult.TIMEOUT);
return cmdResult;
}
cmdResult.setErrorCode(exitValue);
if (exitValue != ShellResult.SUCCESS) {
cmdResult.setDescription(errorGobbler.getOutput());
} else {
cmdResult.setDescription(outputGobbler.getOutput());
}
} catch (InterruptedException e) {
LOGGER.error("Get shell result error.");
cmdResult.setErrorCode(ShellResult.ERROR);
} finally {
CommonUtils.closeStream(process.getErrorStream());
CommonUtils.closeStream(process.getInputStream());
CommonUtils.closeStream(process.getOutputStream());
}
return cmdResult;
}
}
StreamGobbler:读取命令输出流和错误流的工具类
package com.wll.shell;
import com.wll.utils.CommonUtils;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class StreamGobbler extends Thread {
private InputStream is;
private List<String> output = new ArrayList<String>();
public StreamGobbler(InputStream is) {
this.is = is;
}
public List<String> getOutput() {
return output;
}
@Override
public void run() {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String line = "";
while ((line = reader.readLine()) != null) {
output.add(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
CommonUtils.closeStream(reader);
}
}
}
ShellResult:命令执行结果
package com.wll.shell;
import java.util.List;
public class ShellResult {
public static final int SUCCESS = 0;
public static final int ERROR = 1;
public static final int TIMEOUT = 13;
private int errorCode;
private List<String> description;
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
public List<String> getDescription() {
return description;
}
public void setDescription(List<String> description) {
this.description = description;
}
@Override
public String toString() {
return "ShellResult{" +
"errorCode=" + errorCode +
", description=" + description +
'}';
}
}
ShellTest:测试类
package com.wll.shell;
public class ShellTest {
public static void main(String[] args) {
String cmd = "";
if (args.length == 1) {
cmd = args[0];
}
ShellUtils.runShell(cmd);
}
}
另外,由于流关闭操作用得比较频繁,故单独写了个工具类。
package com.wll.utils;
import org.apache.log4j.Logger;
import java.io.Closeable;
import java.io.IOException;
public class CommonUtils {
private static final Logger LOGGER = Logger.getLogger(CommonUtils.class);
/**
* 提供统一关闭流的方法
*
* @param stream 待关闭的流
*/
public static void closeStream(Closeable stream) {
if (stream == null) {
return;
}
try {
stream.close();
} catch (IOException e) {
LOGGER.error("Close stream failed!");
}
}
}
代码放在CentOS下,详细目录结构如下:
以下为测试脚本,十分简单,只是输出当前日期和时间
![](https://i-blog.csdnimg.cn/blog_migrate/006da04b6aa074af116d2045129dd3ce.png)
最终运行结果如下: