【问题标题】:Execute command displaying console window and also get handle of the process执行显示控制台窗口的命令并获取进程的句柄
【发布时间】:2018-07-23 18:48:42
【问题描述】:

我正在尝试从 Java 运行一个命令,该命令将启动一个运行几分钟的进程。我只需要触发命令并获取进程句柄并在循环中继续执行其他操作。每隔一段时间,我将不得不监视该过程是否仍然处于活动状态。

我还需要显示控制台窗口以向用户显示进程的输出。

目前,我尝试了 Runtime 和 ProcessBuilder 类中的方法来运行我的命令,但它们都没有帮助我实现目标。

示例代码:

//Changing the directory and running Maven exec: java command on the POM file in that directory.

String cmd = "cd C:/Test & mvn exec:java"; 
String finalCmd = "cmd /c \""+ cmd +"\"";

Process process = Runtime.getRuntime().exec(finalCmd);
Thread.sleep(10);
boolean alive = process.isAlive();

变量alive的值为True,但我没有看到进程启动。程序执行完成后,才开始该过程,我不确定为什么会发生这种情况。

另外为了显示控制台窗口,我从google发现我需要使用以下命令:

String finalCmd = "cmd /c start cmd.exe /c \"" + cmd + "\"";

但是,有了这个,进程会立即启动,但我没有得到进程句柄,因为我发现 live 变量显示为 false。

有人知道如何实现这个目标吗?如果不能同时执行这两项操作,我可以,但至少我需要启动流程执行并获取句柄,以便稍后在我的代码中监控流程状态。

【问题讨论】:

    标签: java command-line process


    【解决方案1】:

    这里发生了一些错误的事情:

    • 我们需要将命令作为字符串标记传递给 exec() 命令
    • 我们需要等待进程退出 process.waitFor() 而不是休眠,这将阻塞当前线程,因此如果您不希望在另一个线程中执行此操作或使用 ExecutorService。
    • 建议检查 waitFor() 的输出值,以查看我们的命令是否正确执行(值为 0)或未正确执行(任何其他值, 如果执行不成功,通常为正 1)
    • 可选(要查看输出)我们需要将标准 OUT 和 ERR 重定向到某个地方,例如将其打印到 console(),尽管您可以将其放入某个 GUI 窗口等文件中。

    所以至少以下代码应该可以工作:

    Process process = Runtime.getRuntime().exec(new String[] {"cmd", "/c", "cd", "C:\\dev", "&&", "dir"});
    int outputVal = process.waitFor();
    boolean alive = process.isAlive();
    System.out.format("alive %s, outputVal: %d\n",alive, outputVal);
    

    进一步的建议:

    • 使用 ProcessBuilder 代替 runTime.exec(),它允许更多的控制 并且是自 JDK 1.5 以来推荐的方式
    • 读取输入流

    所以代码看起来像这样:

        List<String> cmdList = Arrays.asList("cmd", "/c", "cd", "C:\\dev", "&&", "dir");
        ProcessBuilder pb = new ProcessBuilder(cmdList);
        pb.redirectErrorStream(true); //redirect STD ERR to STD OUT
        Process process = pb.start();
        try (final BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String line = null;
            while ((line = br.readLine()) != null) {
                  System.out.println("std-out-line: " + line);
            }
        }
        int outputVal = process.waitFor();
        System.out.format("outputVal: %d\n", outputVal);
    

    由于 waitFor() 是一个阻塞调用,您可以在单独的线程中执行它或使用 executorService。示例代码:

        final StringBuffer outputSb = new StringBuffer();
        ExecutorService executorService = null;
        try {
            executorService = Executors.newSingleThreadExecutor();
            final Future<Integer> future = executorService.submit(new Callable<Integer>() {
    
                @Override
                public Integer call() throws Exception {
                    try (final BufferedReader br = new BufferedReader(
                            new InputStreamReader(process.getInputStream()))) {
                        String line = null;
                        while ((line = br.readLine()) != null) {
                            outputSb.append("std-out-line: ");
                            outputSb.append(line);
                            outputSb.append('\n');
                        }
                    }
                    int exitValue = process.waitFor();
                    System.out.format("exitValue: %d\n", exitValue);
    
                    return exitValue;
                }
            });
    
            while (!future.isDone()) {
                System.out.println("Waiting for command to finish doing something else..");
                Thread.sleep(1 * 1000);
            }
    
            int exitValue = future.get();
            System.out.println("Output: " + outputSb);
    
        } finally {
            executorService.shutdown();
        }
    

    【讨论】:

    • 不确定我们是否在同一页面上。就像我在描述中提到的那样,我需要触发该过程并对其进行处理。我不能等到它完成,因为我需要立即开始其他操作,比如解析一些文件等。但是,我需要在定期执行我的其他操作时监控该进程是否正在运行。如果该进程未处于活动/运行状态,我将停止我的其他操作。让我知道现在是否清楚。
    • 我添加了一些代码来使用 Callable 和 ExecutorService 异步运行部分代码,希望对您有所帮助。希望你能理解流程。此外,如果不将您的 cmd 拆分为适当的令牌并将其传递给 runtTime.exec() 或 ProcessBuilder,您的进程将返回一个错误退出值并且一开始就无法工作。
    【解决方案2】:

    这是一个使用 WMIC 的解决方案。

    public static void main( String[] args ) throws Exception {
    
        // Vars
        Process process;
        String output;
    
        // Execution
        process = Runtime.getRuntime().exec("cmd /c wmic process call create calc.exe | findstr ProcessId");
        output = readTrimmedOutput(process.getInputStream());
        System.out.println("Output from command: " + output);
    
        // Basic string manipulation to get process id
        String str_proc_id = output.split(" = ")[1].replace(";","");
        System.out.println("ProcessId is: " + str_proc_id);
    
        // Some thread delay that you can comment/uncomment for testing if running or not
        Thread.sleep(5000);
    
        // Finding if process is still running
        process = Runtime.getRuntime().exec("cmd /c wmic process get processid | findstr " + str_proc_id);
        output = readTrimmedOutput(process.getInputStream());
    
        boolean isRunning = output.contains(str_proc_id);
        System.out.println("Is process still running? " + isRunning);
    
    }
    
    private static String readTrimmedOutput(InputStream is) throws Exception {
        BufferedReader breader = new BufferedReader(new InputStreamReader(is));
        String line = breader.readLine();
        return line != null ? line.trim() : "";
    }
    

    样本输出

    Output from command: ProcessId = 6480;
    ProcessId is: 6480
    Is process still running? true
    

    为了显示/显示 cmd 控制台,将一些行更改为:

    // Execution
    String your_command = "cmd.exe /c \"dir\"";
    process = Runtime.getRuntime().exec("cmd /c wmic process call create \"" + your_command + "\" | findstr ProcessId");
    

    参考资料:

    https://msdn.microsoft.com/en-us/library/aa394531(v=vs.85).aspx

    https://www.computerhope.com/wmic.htm

    【讨论】:

    • 此解决方案运行良好,没有任何问题。但是,当我调用代码检查进程是否仍在循环中运行时,WMIC 似乎会导致性能问题 w.r.t CPU 使用率。但我认为我可以在每次迭代之间使用更高的睡眠周期来处理这个问题。谢谢。
    【解决方案3】:

    因为我不太明白你真正需要什么,所以我带来了一个从 java 类(例如 A 类)打开 cmd 并启动另一个 java 类(B 类)的进程并从B 类,而 B 类正在通知 A 类它是否正在处理。所以整个事情就是从 A 类启动的命令提示符中执行 B 类,并从 B 类向 A 发送信息以通知它它仍在运行。

    在我的示例中,我将 Main class 作为 A 类,将 myProcess class 作为 B 类。 正如您在下面的代码中看到的,Main class 正在打开 cmd 并正在执行 myProcess class 然后 myProcess class 正在通过在 Main class 中创建的套接字发送有关进程的信息

    //imports
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    //class
    public class Main 
    {
      //fields
    
      //methods
    
    
        public static void main(String[] args) throws Exception
        {
    
            Runtime run = Runtime.getRuntime();
            String new_dir = "C:\\Users\\Parsa\\Desktop\\New folder (2)";//imagine the directory of myProcess.class is in this folder
            startServer();
            run.exec("cmd.exe /c cd \""+new_dir+"\" & start cmd.exe /k \"java myProcess\"");
    
        }
    
    
        public static void startServer() 
        {
            Thread myThread = new Thread() 
            {
                @Override
                public void run() 
                {
                    ServerSocket ss;// creating an open port for receiving data from network
                    try {
                        ss = new ServerSocket(60010);//open port number 60010--> it can really be anything that is empty
    
                        Socket s = ss.accept();//Listens for a connection to be made to this socket and accepts it
    
                        BufferedReader in = new BufferedReader(
                                new InputStreamReader(s.getInputStream()));//get the inputstream and change it to a buffered reader in order to get a string from remote network
                        String line = null;
                        while ((line = in.readLine()) != null) //read the input
                        {
                            System.out.println(line);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            };
    
            myThread.start();
    
    
        }
    
    }
    

    myProcess 类:

    顺便说一句,您需要通过命令提示符手动编译 myProcess 类并从 Main 类中执行 myProcess.class 文件

    myProcess 类是

    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.net.Socket;
    import java.net.URL;
    import java.net.URLConnection;
    import java.net.UnknownHostException;
    import java.util.Timer;
    import java.util.TimerTask;
    
    public class myProcess extends Thread
    {
      //field
    
      //methods
        public static void main(String[] args) throws Exception
        {
            System.out.println("myProcess has started");
            startSender();
    
        }
    
    
        public static void startSender() 
        {
            Thread myThread = new Thread() 
            {
                @Override
                public void run() 
                {
                    try 
                    {
                        Socket s = new Socket("localhost", 60010);
                        BufferedWriter out = new BufferedWriter(
                                new OutputStreamWriter(s.getOutputStream()));
    
                        for(int i = 0 ; i<10 ; i++)
                        {
                            out.write("Process in running");
                            out.newLine();
                            out.flush();
    
                            Thread.sleep(200);
                        }
                        out.close();
                        //do whatever here
                        System.out.println("myProcess output");
                    } 
                    catch (Exception e) 
                    {
                        e.printStackTrace();
                    } 
                }
            };
            myThread.start();
    
    
            if(!myThread.isAlive())
            {
                System.out.println("myProcess has finished");
            }
        }
    }
    

    因为我没有完全理解你想要什么,这可能不是你想要的,但是......如果你操纵代码它肯定会帮助你。

    【讨论】:

    • 如果您还想要其他东西,请告诉我。如果您准确,我将不胜感激。这是一个非常棘手且重要的问题,如果我能正确回答,我会很高兴。因此,如果有任何我不完全理解的错误,请通知我。但如果我理解正确,上面的代码可以工作,并且让它在你的程序上工作的工作取决于你操纵我的答案。谢谢
    • 感谢您的尝试,但这似乎无法回答问题。让我帮你简化一下。如果您看到这一行 - Process process = Runtime.getRuntime().exec(finalCmd);,我正在尝试运行 finalCmd 字符串中存在的一些命令并获取启动过程的句柄。稍后在我的代码中,我会做其他事情并定期检查这个process 是否仍然处于活动状态。
    • 使用我尝试的解决方案,我要么得到了句柄,但进程没有启动,要么进程开始了,但我没有得到句柄。所以我正在寻找一种解决方案,我可以同时做到这两点。
    • 在这种情况下,Java 9 有一个名为 ProcessHandle 的新进程接口,您可以使用该接口轻松地做您想做的事情
    • 我需要使用 Java 8,所以目前不能切换到 Java 9。
    【解决方案4】:

    我相信您需要将应用程序作为进程而不是 CMD 启动,然后启动 CMD 的子进程。和Linux一样。

    您启动的 CMD 是 alive=true,但是当您从该 CMD 启动 java 时是另一个进程,它是 CMD 的子进程,但它不会返回您预期的结果。

    HTH, 加尔

    PS。你可能想看看https://commons.apache.org/proper/commons-exec/,在我看来它在功能上优于Java。

    【讨论】:

      猜你喜欢
      • 2010-11-19
      • 2011-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多