Java Runtime.getRuntime().exec由表及里
字数 1472 2025-08-25 22:58:55
Java Runtime.getRuntime().exec 命令执行深度解析
问题概述
Java中Runtime.getRuntime().exec()方法用于执行系统命令,但在某些情况下会出现命令执行失败的情况,特别是当命令中包含特殊字符如&、|等时。
问题复现
测试代码
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class linux_cmd1 {
public static void main(String[] args) throws IOException {
String cmd = "cmd which you want to exec";
InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int a = -1;
while ((a = in.read(b)) != -1) {
baos.write(b, 0, a);
}
System.out.println(new String(baos.toByteArray()));
}
}
成功与失败的情况
- 成功情况:执行简单命令如
echo 2333 - 失败情况:执行包含特殊字符的命令如
echo 2333 && echo 2333
表面解释
传统解释:Runtime.getRuntime().exec()执行命令时没有shell上下文环境,无法处理&、|等特殊符号。
解决方案
- 命令编码:对执行命令进行编码
- 使用数组形式:
String[] command = {"/bin/sh", "-c", "echo 2333 2333 2333 && echo 2333 2333 2333"};
InputStream in = Runtime.getRuntime().exec(command).getInputStream();
源码深度分析
字符串参数执行流程
- 进入
java.lang.Runtime#exec(java.lang.String, java.lang.String[], java.io.File) - 关键点1:
StringTokenizer将命令字符串按\t\n\r\f分割成数组cmdarray - 调用
ProcessBuilder.start() ProcessBuilder.start()调用ProcessImpl.start()- 关键点2:
cmdarray[0]作为命令,cmdarray[1:]作为参数转换为字节数组argBlock(以\x00分隔) - 最终调用
UNIXProcess的forkAndExec方法
字符串数组参数执行流程
- 直接进入
java.lang.Runtime#exec(java.lang.String[]) - 跳过
StringTokenizer处理 - 后续流程与字符串情形一致
关键区别
- 字符串模式:
echo 2333 && echo 2333被分割为["echo", "2333", "&&", "echo", "2333"] - 数组模式:
["/bin/sh", "-c", "echo 2333 && echo 2333"]保持完整
JVM底层分析
执行流程
java.lang.Runtime.exec(cmd)java.lang.ProcessBuilder.start()java.lang.ProcessImpl.start()Java_java_lang_UNIXProcess_forkAndExec()(native方法)- 调用系统
fork或vfork或posix_spawn - 最终调用
execvp()
参数传递差异
-
数组模式:
execvp("/bin/bash", ["/bin/bash", "-c", "echo 2333 && echo 2333"]) -
字符串模式:
execvp("/bin/bash", ["/bin/bash", "-c", "'echo", "2333", "&&", "echo", "2333'"])
根本原因
字符串形式下命令执行失败的本质是:
StringTokenizer将命令分割破坏了原始语义execvp本身不解释特殊符号如&、|- 数组形式成功是因为通过
/bin/bash解释执行,由shell处理特殊符号
结论
Runtime.getRuntime().exec()执行字符串命令时,会通过StringTokenizer分割命令- 这种分割会破坏包含特殊符号的命令的原始语义
- 使用数组形式并指定shell解释器可以绕过此限制
- 底层是通过
execvp系统调用执行命令,不涉及shell解释,除非显式调用shell
最佳实践
-
对于简单命令,可以直接使用字符串形式
-
对于包含特殊符号的复杂命令:
- 使用数组形式
- 显式指定shell解释器
- 示例:
String[] command = {"/bin/sh", "-c", "your_command_with_special_chars"}; Runtime.getRuntime().exec(command);
-
避免直接将不可信输入拼接到命令字符串中,以防止命令注入