Java执行命令行命令的工具类RuntimeUtil

一直觉得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));
	}
}

你可能感兴趣的:(java,命令,Runtime)