package cn.exlive.monitor.utils;
import cn.exlive.monitor.config.ProgramsConfig;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecuteResultHandler;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteResultHandler;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.ProcessDestroyer;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.system.ApplicationHome;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* 便捷得 Shell 执行工具类
*
*
*
* Created by zhaopx.
* Date: 2025/6/9
* Time: 12:00
* Vendor: exlive.cn
*
*
*
* @author zhaopx
*/
public class ShellUtils {
private static final Logger logger = LoggerFactory.getLogger(ShellUtils.class);
public static String INSTALL_PATH = getInstallPath();
public final static String OS = SystemUtils.IS_OS_LINUX ? "linux" : "windows";
private static ProcessBuilder processBuilder = new ProcessBuilder();
/**
* 返回 App HOME
* @return
*/
private static String getInstallPath() {
String appHome = System.getenv("APP_HOME");
if(StringUtils.isBlank(appHome)) {
appHome = System.getProperty("app.home");
}
if(StringUtils.isBlank(appHome)) {
// appHome = "D:/Work/Deploy";
ApplicationHome ah = new ApplicationHome(ShellUtils.class);
// 获取jar包所在目录
File source = ah.getSource();
File dir = ah.getDir();
if(source == null && dir == null) {
throw new IllegalStateException("请设置 Monitor APP_HOME 环境变量。");
}
if(source == null && dir != null) {
appHome = dir.getParentFile().getAbsolutePath();
} else {
File appHomeFile = Optional.ofNullable(source.getParentFile()).map(File::getParentFile).orElseGet(()->{
String userDir = System.getProperty("user.dir");
return new File(org.springframework.util.StringUtils.hasLength(userDir) ? userDir : ".");
});
appHome = appHomeFile.getAbsolutePath();
}
logger.warn("use app home: {} recommen user set APP_HOME envirment.", appHome);
}
// monitor app home 的上一层是 INSTALL_DIR
File homeDir = new File(appHome);
String installPath = homeDir.getParentFile().getAbsolutePath();
logger.info("INSTALL_PATH: {}", installPath);
File dataDir = new File(homeDir, "data");
if(!dataDir.exists()) {
dataDir.mkdir();
logger.info("mkdir monitor data dir: {}", dataDir.getAbsolutePath());
}
return installPath;
}
/**
* @param pathOrCommand 脚本路径或者命令
* @return
*/
public static ExecResult exceShell(String pathOrCommand) {
ExecResult result = new ExecResult();
StringBuffer stringBuffer = new StringBuffer();
try {
// 执行脚本
Process ps = Runtime.getRuntime().exec(new String[]{"sh", "-c", pathOrCommand});
// 只能接收脚本echo打印的数据,并且是echo打印的最后一次数据
BufferedInputStream in = new BufferedInputStream(ps.getInputStream());
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = br.readLine()) != null) {
stringBuffer.append(line);
stringBuffer.append(System.lineSeparator());
}
in.close();
br.close();
String execOut = stringBuffer.toString();
int exitValue = ps.waitFor();
result.setReturnCode(exitValue);
if (0 == exitValue) {
logger.info("{} command exec out is : {} {}", pathOrCommand, System.lineSeparator(), execOut);
result.setExecResult(true);
result.setExecOut(execOut);
} else {
result.setExecOut("call shell failed. error code is :" + exitValue);
logger.error("{} command exec out is : {} {}", pathOrCommand, System.lineSeparator(), execOut);
}
} catch (Exception e) {
result.setExecOut(e.getMessage());
e.printStackTrace();
}
return result;
}
// 获取cpu架构 arm或x86
public static String getCpuArchitecture() {
try {
Process ps = Runtime.getRuntime().exec("arch");
StringBuffer stringBuffer = new StringBuffer();
int exitValue = ps.waitFor();
if (0 == exitValue) {
// 只能接收脚本echo打印的数据,并且是echo打印的最后一次数据
BufferedInputStream in = new BufferedInputStream(ps.getInputStream());
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = br.readLine()) != null) {
logger.info("脚本返回的数据如下: " + line);
stringBuffer.append(line);
}
in.close();
br.close();
return stringBuffer.toString();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static ExecResult execWithStatus(String workPath, List command, long timeout) {
Process process = null;
ExecResult result = new ExecResult();
try {
processBuilder.directory(new File(workPath));
processBuilder.environment();
processBuilder.command(command);
processBuilder.redirectErrorStream(true);
process = processBuilder.start();
printOutput(process);
boolean execResult = process.waitFor(timeout, TimeUnit.SECONDS);
result.setReturnCode(process.exitValue());
if (execResult && process.exitValue() == 0) {
logger.info("script execute success");
result.setExecResult(true);
result.setExecOut("script execute success");
} else {
result.setExecOut("script execute failed");
}
return result;
} catch (Exception e) {
result.setExecErrOut(e.getMessage());
e.printStackTrace();
}
return result;
}
public static ExecResult execWithLogs(String workPath, List command, long timeout) {
Process process = null;
ExecResult result = new ExecResult();
try {
processBuilder.directory(new File(workPath));
processBuilder.environment();
processBuilder.command(command);
processBuilder.redirectErrorStream(true);
process = processBuilder.start();
String output = getOutput(process);
boolean execResult = process.waitFor(timeout, TimeUnit.SECONDS);
result.setReturnCode(process.exitValue());
result.setExecOut(output);
if (execResult && process.exitValue() == 0) {
logger.info("script execute success");
result.setExecResult(true);
} else {
}
return result;
} catch (Exception e) {
result.setExecErrOut(e.getMessage());
e.printStackTrace();
}
return result;
}
public static ExecResult execWithStatus(String workPath, List command, long timeout, Logger logger) {
Process process = null;
ExecResult result = new ExecResult();
try {
processBuilder.directory(new File(workPath));
processBuilder.environment();
processBuilder.command(command);
processBuilder.redirectErrorStream(true);
process = processBuilder.start();
getOutput(process, logger);
boolean execResult = process.waitFor(timeout, TimeUnit.SECONDS);
result.setReturnCode(process.exitValue());
if (execResult && process.exitValue() == 0) {
logger.info("script execute success");
result.setExecResult(true);
result.setExecOut("script execute success");
} else {
result.setExecOut("script execute failed");
}
return result;
} catch (Exception e) {
result.setExecErrOut(e.getMessage());
e.printStackTrace();
}
return result;
}
public static void getOutput(Process process, Logger logger) {
CompletableFuture.runAsync(() -> {
BufferedReader inReader = null;
try {
inReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
StringBuffer stringBuffer = new StringBuffer();
while ((line = inReader.readLine()) != null) {
stringBuffer.append(line);
stringBuffer.append(System.lineSeparator());
}
logger.info(stringBuffer.toString());
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
closeQuietly(inReader);
}
BufferedReader errorReader = null;
try {
errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String line;
StringBuffer stringBuffer = new StringBuffer();
while ((line = errorReader.readLine()) != null) {
stringBuffer.append(line);
stringBuffer.append(System.lineSeparator());
}
logger.error(stringBuffer.toString());
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
closeQuietly(errorReader);
}
});
}
public static String getOutput(Process process) {
StringBuffer stringBuffer = new StringBuffer();
BufferedReader inReader = null;
try {
inReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = inReader.readLine()) != null) {
stringBuffer.append(line);
stringBuffer.append(System.lineSeparator());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
closeQuietly(inReader);
}
return stringBuffer.toString();
}
public static void printOutput(Process process) {
CompletableFuture.runAsync(() -> {
BufferedReader inReader = null;
try {
inReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
StringBuffer stringBuffer = new StringBuffer();
while ((line = inReader.readLine()) != null) {
stringBuffer.append(line);
stringBuffer.append(System.lineSeparator());
}
logger.trace(stringBuffer.toString());
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
closeQuietly(inReader);
}
});
}
public static String getError(Process process) {
String errput = null;
BufferedReader reader = null;
try {
if (process != null) {
StringBuffer stringBuffer = new StringBuffer();
reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
while (reader.read() != -1) {
stringBuffer.append("\n" + reader.readLine());
}
errput = stringBuffer.toString();
}
} catch (Exception e) {
e.printStackTrace();
}
closeQuietly(reader);
return errput;
}
public static ExecResult exec(String commands) {
return exec(null, CommandLine.parse(commands), new HashMap<>(), -1, null);
}
public static ExecResult exec(String commands, int timeoutSecond) {
return exec(null, CommandLine.parse(commands), new HashMap<>(), timeoutSecond, null);
}
public static ExecResult exec(String workPath, String commands) {
return exec(workPath, CommandLine.parse(commands), new HashMap<>(), -1, null);
}
public static ExecResult exec(String workPath, String commands, int timeoutSecond) {
return exec(workPath, CommandLine.parse( commands), new HashMap<>(), timeoutSecond, null);
}
/**
* 执行命令,并返回回调信息
* @param workPath
* @param commands
* @param timeoutSecond
* @param executeResultHandler
* @return
*/
public static ExecResult exec(String workPath, String commands, int timeoutSecond, BrodcastExecuteResultHandler executeResultHandler) {
return exec(workPath, CommandLine.parse( commands), new HashMap<>(), timeoutSecond, executeResultHandler);
}
public static ExecResult exec(String workPath, String commands, Map envs, int timeoutSecond) {
return exec(workPath, CommandLine.parse( commands), envs, timeoutSecond, null);
}
/**
* 执行命令,并返回回调信息
* @param workPath
* @param commands
* @param timeoutSecond
* @param executeResultHandler
* @return
*/
public static ExecResult exec(String workPath, String commands, Map envs, int timeoutSecond, BrodcastExecuteResultHandler executeResultHandler) {
return exec(workPath, CommandLine.parse( commands), envs, timeoutSecond, executeResultHandler);
}
public static ExecResult exec(List commands) {
return exec(null, CommandLine.parse(StringUtils.join(commands, " ")), new HashMap<>(), -1, null);
}
public static ExecResult exec(List commands, int timeoutSecond) {
return exec(null, CommandLine.parse(StringUtils.join(commands, " ")), new HashMap<>(), timeoutSecond, null);
}
public static ExecResult exec(String workPath, List commands) {
return exec(workPath, CommandLine.parse(StringUtils.join(commands, " ")), new HashMap<>(), -1, null);
}
public static ExecResult exec(String workPath, List commands, int timeoutSecond) {
return exec(workPath, CommandLine.parse(StringUtils.join(commands, " ")), new HashMap<>(), timeoutSecond, null);
}
/**
* 带有回调的执行命令
* @param workPath
* @param commands
* @param timeoutSecond
* @param executeResultHandler
* @return
*/
public static ExecResult exec(String workPath, List commands, int timeoutSecond, BrodcastExecuteResultHandler executeResultHandler) {
return exec(workPath, CommandLine.parse(StringUtils.join(commands, " ")), new HashMap<>(), timeoutSecond, null);
}
public static ExecResult exec(String workPath, CommandLine commandLine, Map envs, int timeoutSecond, DefaultExecuteResultHandler handler) {
DefaultExecutor executor = new DefaultExecutor();
executor.setExitValue(-1);
// handler 记录
handler = (handler == null ? new DefaultExecuteResultHandler() : handler);
ShellLogOutputStream output = new ShellLogOutputStream();
ShellLogOutputStream err = new ShellLogOutputStream();
executor.setStreamHandler(new PumpStreamHandler(output, err));
PidExecuteWatchdog watchDog = null;
// 不限时间
if(timeoutSecond > 0) {
watchDog = new PidExecuteWatchdog(Duration.ofSeconds(timeoutSecond).toMillis(), handler);
executor.setWatchdog(watchDog);
}
String exeScript = "bash";
if (SystemUtils.IS_OS_WINDOWS) {
// windows 系统不需要执行,也没有权限
exeScript = "";
} else {
// Linux, Mac 系统使用 bash 直接执行脚本
exeScript = "bash";
}
// String cmd = StringUtils.join(commands, " ");
if(StringUtils.isNotBlank(workPath)) {
logger.info("from path: {} execute cmd: {}", workPath, commandLine);
executor.setWorkingDirectory(new File(workPath));
} else {
logger.info("execute cmd: " + commandLine);
}
Map environment = new HashMap<>(System.getenv());
if(envs != null && !envs.isEmpty()) {
environment.putAll(envs);
}
ExecResult result = new ExecResult();
final long start = System.currentTimeMillis();
try {
executor.execute(commandLine, environment, handler);
try {
result.setPid(watchDog != null ? watchDog.getProcessPid() : -1);
} catch (Exception ignore) {}
if (timeoutSecond > 0) {
handler.waitFor(Duration.ofSeconds(timeoutSecond));
} else {
handler.waitFor();
}
int exitCode = handler.getExitValue();
result.setUseTime(System.currentTimeMillis() - start);
result.setExecOut(output.toString());
result.setReturnCode(exitCode);
result.setExecErrOut(err.toString());
result.setExecResult(Objects.equals(0, exitCode));
return result;
} catch (Exception e) {
result.setUseTime(System.currentTimeMillis() - start);
result.setExecResult(false);
result.setReturnCode(-1);
result.setExecOut(output.toString());
result.setExecErrOut(ExceptionUtils.getRootCauseMessage(e));
logger.error("execute: " + commandLine + " error.", e);
return result;
}
}
public static void closeQuietly(Reader reader) {
try {
if (reader != null) {
reader.close();
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
public static void destroy(Process process) {
if (process != null) {
process.destroyForcibly();
}
}
public static void addChmod(String path, String chmod) {
ArrayList command = new ArrayList<>();
command.add("chmod");
command.add("-R");
command.add(chmod);
command.add(path);
execWithStatus(INSTALL_PATH, command, 60, logger);
}
public static void addChown(String path, String user, String group) {
ArrayList command = new ArrayList<>();
command.add("chown");
command.add("-R");
command.add(user + ":" + group);
command.add(path);
execWithStatus(INSTALL_PATH, command, 60, logger);
}
}