java 执行shell命令及日志收集避坑指南( 二 )
以上问题 , 如果都能处理得当 , 那么我认为 , 这个调用就是安全的 。 反之则是有风险的 。
不过 , 问题看着虽然多 , 但都是些细化的东西 , 也无需太在意 。 基本上 , 我们通过线程池来控制进程的膨胀问题;通过读取io流来解决异常信息问题;通过调用类型规划内存及用量问题;
3. 完整的shell调用参考说了这么多理论 , 还不如来点实际 。 don't bb, show me the code!
import com.my.mvc.app.common.exception.ShellProcessExecException;import com.my.mvc.app.common.helper.NamedThreadFactory;import lombok.extern.log4j.Log4j2;import org.apache.commons.io.FileUtils;import java.io.BufferedReader;import java.io.File;import java.io.IOException;import java.io.InputStreamReader;import java.nio.charset.Charset;import java.util.List;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 功能描述: Shell命令运行工具类封装 * */@Log4j2public class ShellCommandExecUtil {/*** @see #runShellCommandSync(String, String[], Charset, String)*/public static int runShellCommandSync(String baseShellDir, String[] cmd,Charset outputCharset) throws IOException {return runShellCommandSync(baseShellDir, cmd, outputCharset, null);}/*** 真正运行shell命令** @param baseShellDir 运行命令所在目录(先切换到该目录后再运行命令)* @param cmd 命令数组* @param outputCharset 日志输出字符集 , 一般windows为GBK, linux为utf8* @param logFilePath 日志输出文件路径, 为空则直接输出到当前应用日志中 , 否则写入该文件* @return 进程退出码, 0: 成功, 其他:失败* @throws IOException 执行异常时抛出*/public static int runShellCommandSync(String baseShellDir, String[] cmd,Charset outputCharset, String logFilePath)throws IOException {long startTime = System.currentTimeMillis();boolean needReadProcessOutLogStreamByHand = true;log.info("【cli】receive new Command. baseDir: {}, cmd: {}, logFile:{}",baseShellDir, String.join(" ", cmd), logFilePath);ProcessBuilder pb = new ProcessBuilder(cmd);pb.directory(new File(baseShellDir));initErrorLogHolder(logFilePath, outputCharset);int exitCode = 0;try {if(logFilePath != null) {ensureFilePathExists(logFilePath);//String redirectLogInfoAndErrCmd = " > " + logFilePath + " 2>//cmd = mergeTwoArr(cmd, redirectLogInfoAndErrCmd.split("\\s+"));pb.redirectErrorStream(true);pb.redirectOutput(new File(logFilePath));needReadProcessOutLogStreamByHand = false;}Process p = pb.start();if(needReadProcessOutLogStreamByHand) {readProcessOutLogStream(p, outputCharset);}try {p.waitFor();}catch (InterruptedException e) {log.error("进程被中断", e);setProcessLastError("中断异常:" + e.getMessage());}finally {exitCode = p.exitValue();log.info("【cli】process costTime:{}ms, exitCode:{}",System.currentTimeMillis() - startTime, exitCode);}if(exitCode != 0) {throw new ShellProcessExecException(exitCode,"进程返回异常信息, returnCode:" + exitCode+ ", lastError:" + getProcessLastError());}return exitCode;}finally {removeErrorLogHolder();}}/*** 使用 Runtime.exec() 运行shell*/public static int runShellWithRuntime(String baseShellDir,String[] cmd,Charset outputCharset) throws IOException {long startTime = System.currentTimeMillis();initErrorLogHolder(null, outputCharset);Process p = Runtime.getRuntime().exec(cmd, null, new File(baseShellDir));readProcessOutLogStream(p, outputCharset);int exitCode;try {p.waitFor();}catch (InterruptedException e) {log.error("进程被中断", e);setProcessLastError("中断异常:" + e.getMessage());}catch (Throwable e) {log.error("其他异常", e);setProcessLastError(e.getMessage());}finally {exitCode = p.exitValue();log.info("【cli】process costTime:{}ms, exitCode:{}",System.currentTimeMillis() - startTime, exitCode);}if(exitCode != 0) {throw new ShellProcessExecException(exitCode,"进程返回异常信息, returnCode:" + exitCode+ ", lastError:" + getProcessLastError());}return exitCode;}/*** 确保文件夹存在** @param filePath 文件路径* @throws IOException 创建文件夹异常抛出*/public static void ensureFilePathExists(String filePath) throws IOException {File path = new File(filePath);if(path.exists()) {return;}File p = path.getParentFile();if(p.mkdirs()) {log.info("为文件创建目录: {} 成功", p.getPath());return;}log.warn("创建目录:{} 失败", p.getPath());}/*** 合并两个数组数据** @param arrFirst 左边数组* @param arrAppend 要添加的数组* @return 合并后的数组*/public static String[] mergeTwoArr(String[] arrFirst, String[] arrAppend) {String[] merged = new String[arrFirst.length + arrAppend.length];System.arraycopy(arrFirst, 0,merged, 0, arrFirst.length);System.arraycopy(arrAppend, 0,merged, arrFirst.length, arrAppend.length);return merged;}/*** 删除以某字符结尾的字符** @param originalStr 原始字符* @param toTrimChar 要检测的字* @return 裁剪后的字符串*/public static String trimEndsWith(String originalStr, char toTrimChar) {char[] value = http://kandian.youth.cn/index/originalStr.toCharArray();int i = value.length - 1;while (i> 0}return new String(value, 0, i + 1);}/*** 错误日志读取线程池(不设上限)*/private static final ExecutorService errReadThreadPool = Executors.newCachedThreadPool(new NamedThreadFactory("ReadProcessErrOut"));/*** 最后一次异常信息*/private static final MaplastErrorHolder = new ConcurrentHashMap
- 现状|程序员现状揭秘:平均年薪20.36万,Java人才需求量最大
- 程序员学英语第1天——JavaScript 程序测试的介绍1
- 三年Java开发,刚从美团、京东、阿里面试归来,分享个人面经
- 《深入理解Java虚拟机》:对象创建、布局和访问全过程
- 落地|银行又一新规来了,10号起取款“新规”落地,已有两地开始在执行
- java面试题整理
- Kotlin集合vs Kotlin序列与Java流
- Java安全之Javassist动态编程
- 推荐Java工程师必看,12个Hadoop领域的上手项目
- 震惊!京东T4大佬面试整整三个月,才写了两份java面试笔记