我想要通过命令行执行一个程序 类似于 java -jar xxx.jar,然后获得该程序的进程 ID ,并稍后通过进程 ID 判断该进程是否存活。
//start a process
String command = "...";
ProcessBuilder pb = new ProcessBuilder(command);
Process process = pb.start();
//get the pid of process
if (System.getProperty("os.name").toLowerCase().contains("mac")) {
Class<?> clazz = Class.forName("java.lang.UNIXProcess");
field = clazz.getDeclaredField("pid");
ReflectionUtils.makeAccessible(field);
pid = (Integer) field.get(process);
}
最后我希望通过进程 id 判断该进程是否存活,但是我在查进程的时候发现 根本查不到我的那个进程号
// judge the process is alive or not. By pid.
if (System.getProperty(Constants.SYSTEM_NAME).toLowerCase().contains("linux") || System.getProperty(Constants.SYSTEM_NAME).toLowerCase().contains("mac")) {
process = RuntimeUtil.exec(BIN_BASH + " -c" + " ps -elf | grep " + pid);
}
if(process != null){
String line;
try(InputStream in = process.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in,StandardCharsets.UTF_8))){
while((line = br.readLine()) != null){
if(line.contains(pid)){
//输出流中读不到我之前获得的那个进程 ID
return true;
}
}
} catch (IOException e) {
//exception handle
}
}
1
ysc3839 2022-09-28 06:03:01 +08:00 via Android
感觉是 RuntimeUtil.exec 的问题。
先说正常的解法:判断 PID 对应的进程是否存在应该用更底层的 API ,比如 Linux 下读 /proc/<PID>,而不应该用命令行程序来处理。我相信 Java 有现成的支持多平台的库,完全不需要自己写。 再解释你遇到的问题:首先类 Unix 系统的进程参数是字符串数组而不是一个字符串,比如你执行 ps -elf ,ps 进程接收到的参数一般是["ps", "-elf"].因此在执行单个字符串的“命令”时,肯定要有个程序先解析成字符串数组再传递给目标进程。 在 shell 中执行的话,是由 shell 进行解析的,但是用别的语言提供的 API ,就得看文档了解清楚是怎么解析的了。根据 Java 的文档,单个字符串的 Runtime.exec 是用 StringTokenizer 来解析的 https://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#exec(java.lang.String,%20java.lang.String[],%20java.io.File) 而 StringTokenizer 则是按" \t\n\r\f"分割 https://docs.oracle.com/javase/7/docs/api/java/util/StringTokenizer.html#StringTokenizer(java.lang.String) 所以你传进去的字符串最终应该是被解析成了[BIN_BASH, "-c", "ps", "-elf", "|", "grep", pid] 而 bash -c 执行的是 -c 后面那个参数,也就等于执行 ps 。你可以试试在 shell 里执行 bash -c echo 1 和 bash -c 'echo 1' 看看有什么区别。 顺带一提,Windows 下进程参数是一个字符串而不是字符串数组,上述逻辑不适用于 Windows 。 |
2
ysc3839 2022-09-28 06:12:31 +08:00 via Android
简单搜索可得知 Java 有内置 ProcessHandle
https://docs.oracle.com/javase/9/docs/api/java/lang/ProcessHandle.html 不过是从 Java 9 才有的 |
3
jorneyr 2022-09-28 08:25:18 +08:00
Java Process 不支持管道吧,有管道的命令我一般都是写入临时 shell 文件,然后执行 shell 文件。
|
4
jorneyr 2022-09-28 08:27:25 +08:00
```java
package cmd; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.ExecuteWatchdog; import org.apache.commons.exec.PumpStreamHandler; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; /** * 生成临时 shell 脚本并执行 */ public class ExecTempShellScript { public static void main(String[] args) throws IOException { // 1. 生成临时脚本文件 // 2. 命令写入脚本文件 // 3. 执行脚本 // 4. 删除临时脚本文件 String command = "ls -l /Users/biao"; Path path = Files.createTempFile("mongo-", ".sh"); Files.write(path, command.getBytes(StandardCharsets.UTF_8)); System.out.println(path); try { execSh(path.toString()); } finally { Files.delete(path); } } public static void execSh(String path) throws IOException { CommandLine cmdLine = CommandLine.parse("sh " + path); DefaultExecutor executor = new DefaultExecutor(); executor.setExitValues(null); ExecuteWatchdog watchdog = new ExecuteWatchdog(60000); executor.setWatchdog(watchdog); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream,errorStream); executor.setStreamHandler(streamHandler); executor.execute(cmdLine); // 获取程序外部程序执行结果 String out = outputStream.toString("UTF-8"); String error = errorStream.toString("UTF-8"); // 处理结果 System.out.println("==== ok ===="); System.out.println(out); System.out.println("==== error ===="); System.out.println(error); } } ``` |
5
julyclyde 2022-09-28 14:02:12 +08:00
我觉得可能是需求有问题
(当然并不排除你的解法也有问题) 如果你想要亲自做一个“运行一个程序然后监控它”,那么,选择自己做就已经错了 |
6
iminto 2022-09-28 14:46:28 +08:00 via Android
代码就错了,Java 执行 shell 命令不支持特殊字符的
|
7
iminto 2022-09-28 14:47:28 +08:00 via Android
需要转成字符串数组传递,百度一下甚至都能知道
|
8
hahaha777 2022-09-30 13:50:32 +08:00
RuntimeUtil.exec ,参数可能有问题。用数组。
|
9
gzk329 OP @ysc3839 感谢回答 基本已经解决我的问题了 谢谢
我用的这个是 RuntimeUtil.exec()是 hutools 提供的 api 可能是我没用明白 换成 jdk 提供的 Runtime.getRuntime().exec(commands) 按照您说的正确方式传参就没问题 new ProcessBuilder(command, arg1, arg2) 但是我还有一个问题 我在 java 程序中起的进程算是子进程吗? 是 ps -elf 这类命令是查不到子进程吗 我发现我起一个进程 拿到进程号 用 ps -elf 就查不到 如果是 linux 读 /proc 能读到,mac 使用 lsof -p 也能读到 |