# 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 的使用方式。