一直觉得Fedora的desktop文件,写脚本问题颇多,例如上次准备把启动Tomcat的快捷方式添加个按钮,发现每次点的时候都会启动一个会话,启动脚本结束之后,本来已经启动的Tomcat自己就结束了。最后还是改Tomcat的catalina.sh加了一个nohup了事。
上面是说了堆题外话了,总之自己觉得desktop文件写起来不是很习惯,最后自己决定做个Web版本的启动器。这个RuntimeUtil就是作为其工具类完成的,支持命令管道,指定工作目录和环境变量,暂不支持重定向,当然也可以通过调用sh命令来实现,提供了阻塞和非阻塞两种调用方式。
package fox.tools.util; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.StringReader; import java.util.Arrays; import java.util.UUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Runtime工具类,用于本机执行某些命令<br> * * TODO: 重定向标准输入输出和错误流 * * @author fox * @version 0.1.0 */ public class RuntimeUtil { // Log private static Log log = LogFactory.getLog(RuntimeUtil.class); // 当前Runtime private static Runtime runtime = Runtime.getRuntime(); // 工具类不可实例化 private RuntimeUtil() { } /** * 以非阻塞模式执行命令 * * @param callback * 回调类,当命令全部执行完成后会执行其onExit方法,null表示不进行回调 * @param envp * 运行的上下文环境变量,每项都应该写成name=value的格式;null表示直接继承当前Java进程的全部环境变量 * @param dir * 命令执行的工作目录;null表示继承当前Java进程的工作目录 * @param startCommand * 起始命令 * @param commands * 其他后续命令,如果有设置,会使用管道来关联前后命令的标准输出流和标准输入流 */ public static void runAsUnblocking(final RuntimeCallback callback, final String[] envp, final File dir, final String[] startCommand, final String[]... commands) { // 非阻塞的实现就是交给其他函数执行 new Thread(new Runnable() { @Override public void run() { StringReader result = RuntimeUtil.run(envp, dir, startCommand, commands); // 如果需要回调的话,调用回调函数 if (callback != null) { callback.onExit(result, envp, dir, startCommand, commands); } } }).start(); } /** * 以阻塞模式执行命令 * * @param envp * 运行的上下文环境变量,每项都应该写成name=value的格式;null表示直接继承当前Java进程的全部环境变量 * @param dir * 命令执行的工作目录;null表示继承当前Java进程的工作目录 * @param startCommand * 起始命令 * @param commands * 其他后续命令,如果有设置,会使用管道来关联前后命令的标准输出流和标准输入流 * @return 命令执行后的最终结果 */ public static StringReader run(String[] envp, File dir, String[] startCommand, String[]... commands) { // 生成一个命令id,只是为日志区别用 String commandId = UUID.randomUUID().toString(); log.info("Command(" + commandId + ") start"); StringBuilder result = new StringBuilder(); Process currentProcess = null; Process nextProcess = null; try { // 见执行起始命令 currentProcess = run(commandId, startCommand, envp, dir); // 用于管道的字节输出流 BufferedOutputStream out = null; // 用于标准错误输出和最终结果的字符输入流 BufferedReader reader = null; // 用于管道的字节输入流 BufferedInputStream in = null; // 用于管道的IO缓冲 byte[] buffer = new byte[1024]; try { // 遍历后续命令 for (String[] command : commands) { try { // 获取当前命令的标准错误流 reader = new BufferedReader(new InputStreamReader(currentProcess.getErrorStream())); // 输出错误 for (String temp = readLine(reader); temp != null; temp = readLine(reader)) { log.warn("Command(" + commandId + ") Error: " + temp); } // 获取当前命令的标准输出流 in = new BufferedInputStream(currentProcess.getInputStream()); // 启动下一条命令 nextProcess = run(commandId, command, envp, dir); // 获取下一条命令的标准输入流 out = new BufferedOutputStream(nextProcess.getOutputStream()); // 管道的实现 for (int c = read(in, buffer); c >= 0; c = read(in, buffer)) { write(out, buffer, c); } // 当前命令全部输出都已经输入到下一条命令中后,将下一条命令作为当前命令 currentProcess = nextProcess; } finally { // 流关闭操作 if (out != null) { out.flush(); out.close(); out = null; } if (in != null) { in.close(); in = null; } if (reader != null) { reader.close(); reader = null; } } } // 获取最终命令的标准错误流, reader = new BufferedReader(new InputStreamReader(currentProcess.getErrorStream())); // 输出错误 for (String temp = readLine(reader); temp != null; temp = readLine(reader)) { log.warn("Command(" + commandId + ") Error: " + temp); } // 关闭 reader.close(); reader = null; // 获取最终命令的标准输出流 reader = new BufferedReader(new InputStreamReader(currentProcess.getInputStream())); // 将输出添加到结果缓冲中 for (String temp = reader.readLine(); temp != null; temp = reader.readLine()) { result.append(temp + "\n"); } } finally { // 流关闭操作 if (out != null) { out.flush(); out.close(); out = null; } if (in != null) { in.close(); in = null; } if (reader != null) { reader.close(); reader = null; } } } catch (Exception e) { // 出现异常,尝试把启动的进程都销毁 log.warn("Command(" + commandId + ") Error", e); if (currentProcess != null) { currentProcess.destroy(); currentProcess = null; } if (nextProcess != null) { nextProcess.destroy(); nextProcess = null; } } log.info("Command(" + commandId + ") end"); // 用结果缓冲构建StringReader return new StringReader(result.toString()); } /** * 读取StringReader为String * * @param stringReader * StringReader * @param skipWhiteLine * 是否需要跳过空行 * @return */ public static String readStringReader(StringReader stringReader, boolean skipWhiteLine) { StringBuilder result = new StringBuilder(); BufferedReader reader = new BufferedReader(stringReader); for (String temp = readLine(reader); temp != null; temp = readLine(reader)) { if (skipWhiteLine == false || !temp.trim().equals("")) { result.append(temp + "\n"); } } return result.toString(); } /** * 运行一个命令 * * @param commandId * 命令id, 日志区别用 * @param command * 执行的命令 * @param envp * 运行的上下文环境变量,每项都应该写成name=value的格式;null表示直接继承当前Java进程的全部环境变量 * @param dir * 命令执行的工作目录;null表示继承当前Java进程的工作目录 * @return 命令对应的Process对象Process * @throws Exception */ private static Process run(String commandId, String[] command, String[] envp, File dir) throws Exception { log.info("Command(" + commandId + ") exec: " + Arrays.toString(command)); return runtime.exec(command, envp, dir); } /** * 从输入流中读取数据进入buffer中,忽略异常 * * @param in * 输入流 * @param buffer * 缓冲 * @return 基本等同于in.read(buffer)的返回值,不过在出现异常时,不会抛出异常而是返回-1 */ private static int read(InputStream in, byte[] buffer) { int result = -1; try { result = in.read(buffer); } catch (IOException e) { result = -1; } return result; } /** * 从输入流中读取一行字符串,忽略异常 * * @param reader * 输入流 * @return 基本等同于reader.readLine()的返回值,不过在出现异常时,不会抛出异常而是返回null */ private static String readLine(BufferedReader reader) { String result = null; try { result = reader.readLine(); } catch (IOException e) { result = null; } return result; } /** * 向输出流写入buffer从0~length位置的数据,忽略异常 * * @param out * 输出流 * @param buffer * 缓冲 * @param length * 最大位序号 */ private static void write(OutputStream out, byte[] buffer, int length) { try { out.write(buffer, 0, length); } catch (IOException e) { } } /** * 非阻塞模式回调 * * @author fox */ public static interface RuntimeCallback { /** * 非阻塞模式回调函数 * * @param result * 命令执行结果 * @param startCommand * 起始命令 * @param commands * 其他后续命令,如果有设置,会使用管道来关联前后命令的标准输出流和标准输入流 */ public void onExit(StringReader result, String[] envp, File dir, String[] startCommand, String[]... commands); } public static void main(String[] args) { // 例子 ps -ef (注意用String[]方式来表示命令是不需要做转移的) System.out.println(readStringReader(run(null, null, new String[] { "ps", "-ef" }), true)); System.out.println("-----------------------------------------------------------------"); // 例子 ps -ef | grep -i java System.out.println(readStringReader( run(null, null, new String[] { "ps", "-ef" }, new String[] { "grep", "-i", "java" }), true)); } }