我想要通过命令行执行一个程序 类似于 Java -jar xxx.jar,然后获得该程序的进程 ID,并稍后通过进程 ID 判断该进程是否存活?但是在系统中找不到我之前获得的进程号,求帮助?

2022-09-28 00:47:34 +08:00
 gzk329

我想要通过命令行执行一个程序 类似于 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
            }
        }
1918 次点击
所在节点    Java
10 条回复
ysc3839
2022-09-28 06:03:01 +08:00
感觉是 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 。
ysc3839
2022-09-28 06:12:31 +08:00
简单搜索可得知 Java 有内置 ProcessHandle
https://docs.oracle.com/javase/9/docs/api/java/lang/ProcessHandle.html
不过是从 Java 9 才有的
jorneyr
2022-09-28 08:25:18 +08:00
Java Process 不支持管道吧,有管道的命令我一般都是写入临时 shell 文件,然后执行 shell 文件。
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);
}
}
```
julyclyde
2022-09-28 14:02:12 +08:00
我觉得可能是需求有问题
(当然并不排除你的解法也有问题)

如果你想要亲自做一个“运行一个程序然后监控它”,那么,选择自己做就已经错了
iminto
2022-09-28 14:46:28 +08:00
代码就错了,Java 执行 shell 命令不支持特殊字符的
iminto
2022-09-28 14:47:28 +08:00
需要转成字符串数组传递,百度一下甚至都能知道
hahaha777
2022-09-30 13:50:32 +08:00
RuntimeUtil.exec ,参数可能有问题。用数组。
gzk329
2022-09-30 17:41:48 +08:00
@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 也能读到
gzk329
2022-09-30 18:33:48 +08:00
@gzk329 没问题了 是我又搞错了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/883408

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX