V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
gzk329
V2EX  ›  Java

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

  •  
  •   gzk329 · 66 天前 · 1111 次点击
    这是一个创建于 66 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我想要通过命令行执行一个程序 类似于 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
                }
            }
    
    ysc3839
        1
    ysc3839  
       66 天前 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 。
    ysc3839
        2
    ysc3839  
       66 天前 via Android
    简单搜索可得知 Java 有内置 ProcessHandle
    https://docs.oracle.com/javase/9/docs/api/java/lang/ProcessHandle.html
    不过是从 Java 9 才有的
    jorneyr
        3
    jorneyr  
       66 天前
    Java Process 不支持管道吧,有管道的命令我一般都是写入临时 shell 文件,然后执行 shell 文件。
    jorneyr
        4
    jorneyr  
       66 天前
    ```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
        5
    julyclyde  
       66 天前
    我觉得可能是需求有问题
    (当然并不排除你的解法也有问题)

    如果你想要亲自做一个“运行一个程序然后监控它”,那么,选择自己做就已经错了
    iminto
        6
    iminto  
       66 天前 via Android
    代码就错了,Java 执行 shell 命令不支持特殊字符的
    iminto
        7
    iminto  
       66 天前 via Android
    需要转成字符串数组传递,百度一下甚至都能知道
    hahaha777
        8
    hahaha777  
       64 天前
    RuntimeUtil.exec ,参数可能有问题。用数组。
    gzk329
        9
    gzk329  
    OP
       64 天前
    @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
        10
    gzk329  
    OP
       64 天前
    @gzk329 没问题了 是我又搞错了
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2618 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 38ms · UTC 15:30 · PVG 23:30 · LAX 07:30 · JFK 10:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.