• Java Runtime 类详解


    1. Runtime 介绍

    Runtime 是 Java 提供的一个启动子进程来执行命令的方式,它提供了 6 个重载的 exec 方法,用于单独启动一个子进程来执行命令或调用程序。

    • public Process exec(String command) throws IOException
    • public Process exec(String command, String[] envp) throws IOException
    • public Process exec(String command, String[] envp, File dir) throws IOException
    • public Process exec(String cmdarray[]) throws IOException
    • public Process exec(String[] cmdarray, String[] envp) throws IOException
    • public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException

    每一个方法最终都返回一个 Process 对象表示一个进程,从该对象中能够获取进程执行的退出状态码,标准输出流和标准错误流,进而获取进程执行的输出内容。

    前 5 个方法都是间接调用最后一个方法 public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException 。该方法有 3 个参数:

    • cmdarray 命令字符串数组表示,例如执行命令 echo "hello world",对应的参数为 new String[]{"echo","hello world"}
    • envp 字符串数组(可以为 null),表示命令执行过程中设置的环境变量,格式为 name=value,例如:a=1。当为 null 时,(应该,但不一定,取决于 jvm 的实现)继承当前 jvm 进程的环境变量。
    • dir 是一个 File 对象(可以为 null),表示命令执行的工作目录。如果命令中有使用到类似于 ./ 的相对路径,则该相对路径就是基于 dir 的。当为 null 时,工作目录(应该,但不一定,取决于 jvm 的实现)继承当前 jvm 进程的工作目录。

    2. 命令使用一个字符串和使用字符串数组的区别

    public Process exec(String command, String[] envp, File dir) throws IOException

    public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException
    的区别。

    public Process exec(String command, String[] envp, File dir) throws IOException 的实现如下:

    public Process exec(String command, String[] envp, File dir) throws IOException {
        if (command.isEmpty())
            throw new IllegalArgumentException("Empty command");
    
        //使用空格对字符串进行分割
        StringTokenizer st = new StringTokenizer(command);
        String[] cmdarray = new String[st.countTokens()];
        for (int i = 0; st.hasMoreTokens(); i++)
            cmdarray[i] = st.nextToken();
        return exec(cmdarray, envp, dir);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    可以发现,它将 command 命令使用 StringTokenizer空格进行分割,然后组装成数组 cmdarray,最终依然调用的是 exec(String[] cmdarray, String[] envp, File dir) 方法。

    如果不仔细思考,可能觉得这两个重载方法没什么区别,exec(String[] cmdarray, String[] envp, File dir) 不是多此一举吗?

    我们看一个在 linux 系统执行日期修改的命令:

    date -s "2022-11-11 12:00:00"
    
    • 1

    很遗憾使用 exec(String command, String[] envp, File dir) 方法执行该命令无法成功:

    //无法执行成功,会报错
    Runtime.getRuntime().exec("date -s \"2022-11-11 12:00:00\"", null, null);
    
    • 1
    • 2

    这是因为该方法将命令以空格进行分割然后组成数组进行执行,实际上在真正调用
    exec(cmdarray, envp, dir) 为:

    Runtime.getRuntime().exec(new String[]{"date", "-s", "\"2022-11-11", "12:00:00\"", null, null);
    
    • 1

    它将参数 "2022-11-11 12:00:00" 分割成了两个参数 "2022-11-1112:00:00"。所以执行命令时就导致了报错。

    现在应该理解了单字符串命令和命令数组的区别,总结如下:

    • 如果执行的命令的参数中包含空格一定要使用 exec(String[] cmdarray, String[] envp, File dir),否则将导致命令执行失败。
    • 如果执行的命令的参数中没有空格不会导致参数分裂,则两个方法都一样

    3. Runtime 工具类封装

    下面是作者常用的 Runtime 工具类 SystemCommandUtil

    package com.cssth.utils;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Arrays;
    
    /**
     * 系统命令执行
     *
     * @author geng
     * @since 2022/3/22 9:49
     */
    public final class SystemCommandUtil {
        private final static Logger logger = LoggerFactory.getLogger(SystemCommandUtil.class);
    
        /**
         * 不要执行cmd等长时间不会退出的命令,否则会导致启动的子进程长时间不退出!
         *
         * @param command 命令
         */
        public static void exec(String command) {
            execWithExitCode(command);
        }
    
        public static int execWithExitCode(String command) {
            CommandExecResult commandExecResult = execWithExitCodeAndResult(command);
            return commandExecResult.exitCode;
        }
    
        public static CommandExecResult execWithExitCodeAndResult(String[] cmdarray) {
            InputStream errorStream;
            InputStream inputStream;
            int exitCode = -1;
            String stdinOutput = "";
            String errorOutput = "";
            try {
                Process exec = Runtime.getRuntime().exec(cmdarray);
                inputStream = exec.getInputStream();
                stdinOutput = readStringFromInputStream(inputStream);
                logger.warn("执行命令'{}'标准流输出:\n{}", cmdarray, stdinOutput);
                //有些命令就算执行成功也会将输出放到错误流,所以这里也将错误流内容进行读取,便于调试
                errorStream = exec.getErrorStream();
                errorOutput = readStringFromInputStream(errorStream);
                logger.warn("执行命令'{}'错误流输出:\n{}", cmdarray, errorOutput);
    
                exitCode = exec.waitFor();
                if (exitCode != 0) {
                    logger.warn("执行命令'{}'可能失败,退出code:{}!", Arrays.toString(cmdarray), exitCode);
                } else {
                    logger.warn("执行命令'{}'成功,退出code:{}!", Arrays.toString(cmdarray), exitCode);
                }
            } catch (InterruptedException ie) {
                logger.error("执行命令'{}'被中断!", Arrays.toString(cmdarray));
            } catch (IOException e) {
                logger.error("执行命令'" + Arrays.toString(cmdarray) + "'抛出异常!", e);
            }
            return new CommandExecResult(exitCode, stdinOutput, errorOutput);
        }
    
        public static CommandExecResult execWithExitCodeAndResult(String command) {
            InputStream errorStream;
            InputStream inputStream;
            int exitCode = -1;
            String stdinOutput = "";
            String errorOutput = "";
            try {
                Process exec = Runtime.getRuntime().exec(command);
                inputStream = exec.getInputStream();
                stdinOutput = readStringFromInputStream(inputStream);
                logger.warn("执行命令'{}'标准流输出:\n{}", command, stdinOutput);
                //有些命令就算执行成功也会将输出放到错误流,所以这里也将错误流内容进行读取,便于调试
                errorStream = exec.getErrorStream();
                errorOutput = readStringFromInputStream(errorStream);
                logger.warn("执行命令'{}'错误流输出:\n{}", command, errorOutput);
    
                exitCode = exec.waitFor();
                if (exitCode != 0) {
                    logger.warn("执行命令'{}'可能失败,退出code:{}!", command, exitCode);
                } else {
                    logger.warn("执行命令'{}'成功,退出code:{}!", command, exitCode);
                }
            } catch (InterruptedException ie) {
                logger.error("执行命令'{}'被中断!", command);
            } catch (IOException e) {
                logger.error("执行命令'" + command + "'抛出异常!", e);
            }
            return new CommandExecResult(exitCode, stdinOutput, errorOutput);
        }
    
        private static String readStringFromInputStream(InputStream stream) throws IOException {
            byte[] buff = new byte[128];
            int n;
            StringBuilder sb = new StringBuilder();
            try {
                while ((n = stream.read(buff)) != -1) {
                    sb.append(new String(buff, 0, n));
                }
            } finally {
                stream.close();
            }
            return sb.toString();
        }
    
        @SuppressWarnings("unused")
        public static class CommandExecResult {
            private int exitCode;
            private String stdinOutput;
            private String stderrOutput;
    
            public CommandExecResult() {
            }
    
            public CommandExecResult(int exitCode, String stdinOutput, String stderrOutput) {
                this.exitCode = exitCode;
                this.stdinOutput = stdinOutput;
                this.stderrOutput = stderrOutput;
            }
    
            public int getExitCode() {
                return exitCode;
            }
    
            public void setExitCode(int exitCode) {
                this.exitCode = exitCode;
            }
    
            public String getStdinOutput() {
                return stdinOutput;
            }
    
            public void setStdinOutput(String stdinOutput) {
                this.stdinOutput = stdinOutput;
            }
    
            public String getStderrOutput() {
                return stderrOutput;
            }
    
            public void setStderrOutput(String stderrOutput) {
                this.stderrOutput = stderrOutput;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146

    该工具类能够很方便的执行命令并获取命令退出码和输出信息。

  • 相关阅读:
    前端 - 嵌套函数跳出外部函数
    到2026年,超过80%企业将使用生成式AI
    Cpp知识点系列-结构语句
    android桌面插件每秒刷新
    nacos服务注册源码过程阅读
    利用背景渐变实现边框样式
    bootz启动 Linux内核过程总结
    2022年CCNA面试题库和答案
    【kafka】序列化器与反序列化器
    arm交叉编译ntpdate与服务器进行时间同步
  • 原文地址:https://blog.csdn.net/gybshen/article/details/127916652