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上下文环境,无法处理&|等特殊符号。

解决方案

  1. 命令编码:对执行命令进行编码
  2. 使用数组形式
String[] command = {"/bin/sh", "-c", "echo 2333 2333 2333 && echo 2333 2333 2333"};
InputStream in = Runtime.getRuntime().exec(command).getInputStream();

源码深度分析

字符串参数执行流程

  1. 进入java.lang.Runtime#exec(java.lang.String, java.lang.String[], java.io.File)
  2. 关键点1StringTokenizer将命令字符串按\t\n\r\f分割成数组cmdarray
  3. 调用ProcessBuilder.start()
  4. ProcessBuilder.start()调用ProcessImpl.start()
  5. 关键点2cmdarray[0]作为命令,cmdarray[1:]作为参数转换为字节数组argBlock(以\x00分隔)
  6. 最终调用UNIXProcessforkAndExec方法

字符串数组参数执行流程

  1. 直接进入java.lang.Runtime#exec(java.lang.String[])
  2. 跳过StringTokenizer处理
  3. 后续流程与字符串情形一致

关键区别

  • 字符串模式echo 2333 && echo 2333被分割为["echo", "2333", "&&", "echo", "2333"]
  • 数组模式["/bin/sh", "-c", "echo 2333 && echo 2333"]保持完整

JVM底层分析

执行流程

  1. java.lang.Runtime.exec(cmd)
  2. java.lang.ProcessBuilder.start()
  3. java.lang.ProcessImpl.start()
  4. Java_java_lang_UNIXProcess_forkAndExec() (native方法)
  5. 调用系统forkvforkposix_spawn
  6. 最终调用execvp()

参数传递差异

  • 数组模式

    execvp("/bin/bash", ["/bin/bash", "-c", "echo 2333 && echo 2333"])
    
  • 字符串模式

    execvp("/bin/bash", ["/bin/bash", "-c", "'echo", "2333", "&&", "echo", "2333'"])
    

根本原因

字符串形式下命令执行失败的本质是:

  1. StringTokenizer将命令分割破坏了原始语义
  2. execvp本身不解释特殊符号如&|
  3. 数组形式成功是因为通过/bin/bash解释执行,由shell处理特殊符号

结论

  1. Runtime.getRuntime().exec()执行字符串命令时,会通过StringTokenizer分割命令
  2. 这种分割会破坏包含特殊符号的命令的原始语义
  3. 使用数组形式并指定shell解释器可以绕过此限制
  4. 底层是通过execvp系统调用执行命令,不涉及shell解释,除非显式调用shell

最佳实践

  1. 对于简单命令,可以直接使用字符串形式

  2. 对于包含特殊符号的复杂命令:

    • 使用数组形式
    • 显式指定shell解释器
    • 示例:
      String[] command = {"/bin/sh", "-c", "your_command_with_special_chars"};
      Runtime.getRuntime().exec(command);
      
  3. 避免直接将不可信输入拼接到命令字符串中,以防止命令注入

Java Runtime.getRuntime().exec 命令执行深度解析 问题概述 Java中 Runtime.getRuntime().exec() 方法用于执行系统命令,但在某些情况下会出现命令执行失败的情况,特别是当命令中包含特殊字符如 & 、 | 等时。 问题复现 测试代码 成功与失败的情况 成功情况 :执行简单命令如 echo 2333 失败情况 :执行包含特殊字符的命令如 echo 2333 && echo 2333 表面解释 传统解释: Runtime.getRuntime().exec() 执行命令时没有shell上下文环境,无法处理 & 、 | 等特殊符号。 解决方案 命令编码 :对执行命令进行编码 使用数组形式 : 源码深度分析 字符串参数执行流程 进入 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() 参数传递差异 数组模式 : 字符串模式 : 根本原因 字符串形式下命令执行失败的本质是: StringTokenizer 将命令分割破坏了原始语义 execvp 本身不解释特殊符号如 & 、 | 数组形式成功是因为通过 /bin/bash 解释执行,由shell处理特殊符号 结论 Runtime.getRuntime().exec() 执行字符串命令时,会通过 StringTokenizer 分割命令 这种分割会破坏包含特殊符号的命令的原始语义 使用数组形式并指定shell解释器可以绕过此限制 底层是通过 execvp 系统调用执行命令,不涉及shell解释,除非显式调用shell 最佳实践 对于简单命令,可以直接使用字符串形式 对于包含特殊符号的复杂命令: 使用数组形式 显式指定shell解释器 示例: 避免直接将不可信输入拼接到命令字符串中,以防止命令注入