# JAVA 如何调用系统命令

简介:本章主要简述了 java 应用程序执行的过程中,对操作系统输入命令执行,并获取操作系统执行结果。如使用调用 mvn 命令构建工程,其它其它的 java 应用程序,使用第三方服务功能等。大部分时候我们是不需要知道这个技术的,但总有意外。

实现原理:通过 http 请求本地地址建立链接,得到请求的输入流和输出流,通过输入流添加操作系统能执行的命令,通过输入流获取操作系统执行打印结果。(自我理解)

主要类:Process

实现方式类:Runtime 与 jdk1.5 之后的 ProcessBuilder

关系:Runtime 类克隆一个与当前 java 虚拟机环境相同一个进程返回一个 Process 类

注意:每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。可以通过 getRuntime 方法获取当前运行时环境。 应用程序不能创建自己的 Runtime 类实例。

# Runtime API

Process exec(String command) 
          在单独的进程中执行指定的字符串命令。
Process exec(String command, String[] envp) 
          在指定环境的单独进程中执行指定的字符串命令。
Process exec(String command, String[] envp, File dir) 
          在有指定环境和工作目录的独立进程中执行指定的字符串命令。
Process exec(String[] cmdarray) 
          在单独的进程中执行指定命令和变量。 
Process exec(String[] cmdarray, String[] envp) 
          在指定环境的独立进程中执行指定命令和变量。 
Process exec(String[] cmdarray, String[] envp, File dir) 
          在指定环境和工作目录的独立进程中执行指定的命令和变量。

command:一条指定的系统命令。

envp:环境变量字符串数组,其中每个环境变量的设置格式为 name=value;如果子进程应该继承当前进程的环境,则该参数为 null。

dir:子进程的工作目录;如果子进程应该继承当前进程的工作目录,则该参数为 null。

cmdarray:包含所调用命令及其参数的数组。

# Runtime 例子

多行命令
public class CommandUtil {
    /**
     * 执行操作系统命令
     * @param path  执行路径如 windows 的 E:/test/,linux 的: /usr/util/
     * @param commands
     * @return
     */
    public static List<String> command(String path, List<String> commands) {
        List<String> rspList = new ArrayList<String>();
        // 获取这个进程 Runtime 对象
        Runtime run = Runtime.getRuntime();
        try {
            // 第一个参数是:首先执行环境命令,不同操作开启命令方式不一样
            // 第二个参数是:环境变量参数 (jvm 的环境),这里设置为 null 则表示和主环境一致
            // 第三个参数是:设置命令起始执行地址如 windows:E:\\test\;linux:/use/util/;
            //			 相当于执行了一次 cd 命令
            Process proc = run.exec(getOsCmd(), null, new File(path));
            // 获取正确的输出结果
            BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream(),"GB2312"));
            // 获取错误的输出结果
            BufferedReader err = new BufferedReader(new InputStreamReader(proc.getErrorStream(),"GB2312"));
            // 获取一个写的流,并设置 true 则每次执行一次则刷新缓冲区
            PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(proc.getOutputStream())), true);
            // 变量执行命令 (可执行 cd)
            for (String line : commands) {
                out.println(line);
            }
            out.println("exit");// 这个命令必须执行,否则 in 流不结束。
            String rspLine = "";
            while ((rspLine = in.readLine()) != null) {
                rspList.add(rspLine);
            }
            proc.waitFor();// 关闭链接
            in.close();// 关闭输入流
            err.close();// 关闭输入流
            out.close();// 关闭输出流
            proc.destroy();// 结束
        } catch (IOException e1) {
            e1.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return rspList;
    }
}
// 获取当前系统环境并设置新窗口
public static String getOsCmd() {
    Properties props = System.getProperties(); // 获得系统属性集
    String osName = props.getProperty("os.name"); // 操作系统名称
    if (osName.toLowerCase().contains("linux")) {
        return "/bin/bash";
    } else if (osName.toLowerCase().contains("windows")) {
        return "cmd";
    } else {
        throw new BaseRuntimeException("服务器不是linux|windows操作系统");
    }
}

# ProcessBuilder API

// 构造方法 
 ProcessBuilder processBuilder = new ProcessBuilder();
//processBuilder.command("ping","127.0.0.1");
// 利用指定的操作系统程序和参数构造一个进程生成器。 
ProcessBuilder(List<String> command) 
// 利用指定的操作系统程序和参数构造一个进程生成器。
ProcessBuilder(String… command) 
// 方法 
// 返回此进程生成器的操作系统程序和参数。 
command() 
// 设置此进程生成器的操作系统程序和参数。 
command(List<String> command) 
// 设置此进程生成器的操作系统程序和参数。 
command(String… command) 
// 返回此进程生成器的工作目录。 
directory() 
// 设置此进程生成器的工作目录。
directory(File directory) 
// 返回此进程生成器环境的字符串映射视图。 environment 方法获得运行进程的环境变量,得到一个 Map, 可以修改环境变量 
environment() 
// 返回进程生成器是否合并标准错误和标准输出;true 为合并,false 为不合并
redirectErrorStream() 
// 设置此进程生成器的 redirectErrorStream 属性。默认值为 false 不合并
redirectErrorStream(boolean redirectErrorStream) 
// 使用此进程生成器的属性启动一个新进程。
start()

# ProcessBuilder 例子

public class CommandUtil {
    /**
     * 执行操作系统命令
     * @param path  执行路径如 windows 的 E:/test/,linux 的: /usr/util/
     * @param commands
     * @return
     */
    public static List<String> command(String path, List<String> commands) {
        List<String> rspList = new ArrayList<String>();
        try {
            String line = null;
            commands.add(0,getOsCmd())
            commands.add("exit");
            ProcessBuilder pd= new ProcessBuilder(commands);
            // 设置命令起始地址如 windows:E:\\test\;linux:/use/util/
            pd.directory(new File(path));
            pd.redirectErrorStream(true);// 正确与错误日志一起输出
            Process p = pd.start();// 启动
            // 读取返回结果
            BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream(),"GB2312"));
            while((line = in.readLine()) != null){
                rspList.add(line);
			}
            p.waitFor();// 关闭通道
            in.close();// 关闭输入流
        } catch (IOException e1) {
            e1.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return rspList;
    }
}
// 获取当前系统环境并设置新窗口
public static String getOsCmd() {
    Properties props = System.getProperties(); // 获得系统属性集
    String osName = props.getProperty("os.name"); // 操作系统名称
    if (osName.toLowerCase().contains("linux")) {
        return "/bin/bash";
    } else if (osName.toLowerCase().contains("windows")) {
        return "cmd";
    } else {
        throw new BaseRuntimeException("服务器不是linux|windows操作系统");
    }
}

# 总结

​ 在使用的过程中先知道了 Runtime,在使用中也遇到过问题有

1:在操作系统执行命令需要有个起始命令。都是调起命令窗口,这就是为什么结尾都会要加一条 exit 命令最好。

2:Windows 的是 cmd 。如果加 /c 则适合一条整命令的写法。可以理解为主动帮你在命令后面加了个 exit 命令。Linux 的是 /bin/bash 和 /bin/sh。sh 本质上还是 bash。如果加 - c 和前面理解一样

3:返回的字符是 GB2312 编码格式,如果在 windows 上中文就很容易看出乱码,所以上面才转码。

4:Windows 多行命令用 && 拼接,Linux 多行命令用;拼接,但实际 Linux 用 && 也行。

5:多行命令拼接为一条如果起始命令之后是 cd 命令则 windows 上有效,Linux 上无效 (会不会只有我┭┮﹏┭┮)

​ 最后发现 Runtime 与 jdk1.5 之后的 ProcessBuilder 在实现的本质上是一样的,但实现的方式可以看出 ProcessBuilder 的方式更加的简单方便,虽然最后我还是用的是 Runtime 实现 (因为最先看到它),但也理解了 ProcessBuilder 的使用方式。