Java还能经过Runtime.getRuntime().exec()方式调用linux平台下的命令及Shell脚本。java
获取命令执行结果通常有两种,一种是waitfor方式,另外一种是exitValue。
但waitfor方式可能产生阻塞,原因以下:
当调用exec方式后java 执行linux命令,JVM启动一个子进程,该进程会与JVM进程创建3个管路链接,即标准输入流、标准输出流、错误错误流。假定该程序不间断向标准输出流和标准错误流写数据,而JVM不读取,这么数据会暂存在Linux缓冲区中,缓冲区写满后该程序将无法继续写入,程序都会仍然阻塞在waitfor方式,永远无法结束。
解决方式就是下降两个线程java 执行linux命令,一个负责读取标准输出流linux cp,一个负责读取标准错误流,这样数据就不会积压在缓冲区,waitfor方式才能正常结束。
其实linux开源软件,调用外部程序时需要注意如下两点:
一、若是外部程序有大量输出,需要有单独线程读取输出流和错误流
二、必须关掉3个句柄——标准输入流、标准输出流、标准错误流
考虑到阻塞问题以及为了获取命令输出,文中使用了exitValue方式。linux
代码以下shell
ShellUtils:执行外部命令的工具类apache
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:读取命令输出流和错误流的工具类ide
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 output = new ArrayList(); public StreamGobbler(InputStream is) { this.is = is; } public List 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 description; public int getErrorCode() { return errorCode; } public void setErrorCode(int errorCode) { this.errorCode = errorCode; } public List getDescription() { return description; } public void setDescription(List description) { this.description = description; } @Override public String toString() { return "ShellResult{" + "errorCode=" + errorCode + ", description=" + description + '}'; } }
ShellTest:测试类oop
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下,详尽目录结构以下:this
如下为测试脚本,非常简单,只是输出当前日期和时间线程
最终运行结果以下:
文章评论