在 Java 中可用于执行系统命令的方式有三种,分别是java.lang.Runtime,java.lang.ProcessBuilder以及java.lang.UNIXProcess/ProcessImpl。这三种方式都是 JDK 原生提供的,并不需要再额外引入
一. java.lang.Runtime 执行命令
是 java.lang 中的一个类,主要是与操作系统交互执行操作命令。
而在 java.lang.Runtime 中我们主要关注 exec() 方法,使用该方法执行具体的命令,而执行 exec() 方法有以下六种重载形式,可以传入不同的数据类型的参数
| 方法 |
中文释义(非标准) |
| exec(String[] cmdarray) |
在单独的进程中执行 指定的命令和参数。 |
| exec(String command) |
在单独的进程中执行 指定的字符串命令 |
| exec(String command, String[] envp, File dir) |
在具有指定环境和工作目录的单独进程中执行指定的字符串命令。 |
| exec(String command, String[] envp) |
在具有指定环境的单独进程中执行指定的字符串命令 |
| exec(String[] cmdarray, String[] envp) |
在具有指定环境的单 独进程中执行指定的命令和参数。 |
| exec(String[] cmdarray, String[] envp, File dir) |
在具有指定环境和工作目录的单独进程中执行指定的命令和参数。 |
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
| import java.io.*; import java.nio.charset.Charset;
public class ExecRuntime { public static void main(String[] args) throws Exception { String command1 = "cmd /c whoami"; String[] command2 = {"powershell","/c","ping www.baidu.com"}; cmdstring(command1); cmdarray(command2); } private static void cmdstring(String command) throws IOException { String line = null; Process process = Runtime.getRuntime().exec(command); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.forName("GBK"))); while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } } private static void cmdarray(String[] command) throws IOException { String line = null; Process process = Runtime.getRuntime().exec(command); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.forName("GBK"))); while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } } }
|
本质上调用的是 ProcessBuilder 方法:

二. java.lang.ProcessBuilder 执行命令
在上面介绍了java.lang。java.lang.Runtime是其中的一个API。而 java.lang.ProcessBuilder 也是java.lang中的一个API。该类主要用于创建操作系统进程。
2.1 command() 方法
command() 方法主要用于设置要执行的命令。
command() 方法传参有两种方式,一种是可变的字符串(简单说就是可以传入普通字符串,或者字符串数组),另一种是字符串列表
1 2 3
| //简易示例代码 ProcessBuilder p = new ProcessBuilder() p.command("calc")
|
2.2 start() 方法
使用 start() 方法可以创建一个新的具有命令,或环境,或工作目录,或输入来源,或标准输出和标准错误输出的目标,或 redirectErrorStream 属性的进程。
新进程中调用的命令和参数有 command() 方法设置,工作目录将由 directory() 方法设置,进程环境将由 environment() 设置。
在使用 command() 方法设置执行命令参数后,然后由 start() 方法创建一个新的进程进而在系统中执行了我们设置的命令。
这么看来 java.lang.ProcessBuilder#start() 和 Runtime.exec(String[] cmdarray, String[] envp, File dir) 有些相似。
1 2
| Process cmd = new ProcessBuilder(command).start();
|
代码展示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.Charset;
public class ExecProcessBuilder { public static void main(String[] args) throws IOException { ProcessBuilder processBuilder = new ProcessBuilder(); processBuilder.command("cmd.exe", "/c", "ping www.baidu.com"); Process process = processBuilder.start(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.forName("GBK"))); String line; while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } } }
|
补充:
ProcessBuilder在实例化的时候也可以直接执行系统命令,但是没有command方法灵活
1
| ProcessBuilder processBuilder = new ProcessBuilder(("whoami"))
|
start方法实际调用的是 Process start 方法


三. java.lang.UNIXProcess/ProcessImpl 执行命令
首先对于 UNIXProcess 和 ProcessImpl 可以理解本就是一个东西,因为在 JDK9 的时候把UNIXProcess 合并到了 ProcessImpl 当中了。
UNIXProcess 和 ProcessImpl 最终都是调用 native 执行系统命令的类,这个类提供了一个叫 forkAndExec 的native方法,如方法名所述主要是通过 fork&exec 来执行本地系统命令。
UNIXProcess类是*nix系统在 java 程序中的体现,可以使用该类创建新进程,实现与 fork 类似的功能(对于Windows系统,使用的是java.lang.ProcessImpl类)
ProcessImpl 是更为底层的实现,Runtime和ProcessBuilder执行命令实际上也是调用了ProcessImpl这个类。
对于 ProcessImpl 类,我们不能直接调用需要配合使用反射。因为 java.lang.ProcessImpl 代码都被private 封装起来了。并没有设置公共的 API 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import java.io.BufferedReader; import java.io.InputStreamReader; import java.lang.reflect.Method; import java.nio.charset.Charset; import java.util.Map;
public class ExecProcessImpl { public static void main(String[] args) throws Exception { String[] cmds = new String[]{"whoami"}; Class clazz = Class.forName("java.lang.ProcessImpl"); Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class); method.setAccessible(true); Process process = (Process) method.invoke(null, cmds, null, ".", null, true); String line = null; BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.forName("GBK"))); while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } } }
|
invoke()方法: 用于动态调用对象的方法
四. 命令执行总结:
执行命令的类有 Runtime 、ProcessBuilder 和 ProcessImpl,其中Runtime 的 exec方法, ProcessBuilder 的 command方法 和 start方法 ,但 ProcessImpl 这个类是没有办法直接使用的 需要进行反射,调用 ProcessImpl 下面的 start 方法去执行命令
五. 命令执行代码审计: