Java调用Runtime.exec执行docker run挂载volume失败返回125
这个问题和权限、Docker配置没有关系,是Java Runtime.exec(String)的参数拆分逻辑存在设计坑,才导致Docker返回125退出码。
你在终端手动敲命令能正常运行,是因为bash/zsh这类shell会自动解析参数里的引号,把引号包裹的路径识别为单个参数传给Docker进程;但Runtime.exec(String)不会调用shell处理命令,它只会用最简单的空格分割规则拆分字符串,既不会识别你写的转义双引号\",也不会把引号内的内容当成整体参数。
你代码里写的带引号的-v参数,实际传到Docker时,双引号本身成了参数内容的一部分,Docker识别到挂载路径格式非法,直接就退出了。
你之前看不到任何报错也很正常:你只读取了进程的标准输出流,Docker的报错信息是写到标准错误流p.getErrorStream()里的,把这个流的内容打印出来,就能看到明确的挂载参数格式错误提示。
两种可靠修复方式,优先选第一种:
方案1:使用数组传参重载(推荐)
不要传入整行命令字符串,直接把每个参数按顺序拆成字符串数组传入,完全绕开字符串拆分的坑,路径不需要额外加引号转义,哪怕路径里带空格也不会出问题:
public class Test { public static void main(String[] args) throws IOException, InterruptedException { // 每个参数单独作为数组元素,不需要额外加引号 String[] cmd = { "docker", "run", "-d", "-it", "-v", "/Users/myname/Documents/MC_Servers/BungeeCord/plugins/PlayerServers/servers/OpenSourcee:/data", "-p", "32348:32348", "-e", "TYPE=PAPER", "-e", "ONLINE_MODE=false", "-e", "SERVER_PORT=32348", "-e", "EULA=TRUE", "-e", "VERSION=1.8.8", "--name", "OpenSourcee", "itzg/minecraft-server" }; var p = Runtime.getRuntime().exec(cmd, null, null); // 同时读取标准输出和标准错误,避免流缓冲区满导致进程卡死 var stdoutReader = new BufferedReader(new InputStreamReader(p.getInputStream())); var stderrReader = new BufferedReader(new InputStreamReader(p.getErrorStream())); String line; System.out.println("=== 标准输出 ==="); while ((line = stdoutReader.readLine()) != null) { System.out.println(line); } System.out.println("=== 错误输出 ==="); while ((line = stderrReader.readLine()) != null) { System.out.println(line); } System.out.println("进程退出码:" + p.waitFor()); } }
方案2:显式调用shell执行命令
如果一定要复用整行命令字符串,可以显式把命令传给系统shell执行,让shell帮你处理引号解析,逻辑和你在终端手动执行完全一致:
public class Test { public static void main(String[] args) throws IOException, InterruptedException { // 显式调用sh -c 执行完整命令字符串 String[] cmd = {"/bin/sh", "-c", "docker run -d -it -v \"/Users/myname/Documents/MC_Servers/BungeeCord/plugins/PlayerServers/servers/OpenSourcee:/data\" -p 32348:32348 -e TYPE=PAPER -e ONLINE_MODE=false -e SERVER_PORT=32348 -e EULA=TRUE -e VERSION=1.8.8 --name OpenSourcee itzg/minecraft-server"}; var p = Runtime.getRuntime().exec(cmd, null, null); // 流读取、等待退出的逻辑和方案1一致即可 } }
不管用哪种方案,都要同时消费标准输出和标准错误两个流,否则当某个流的缓冲区写满(通常是4KB左右)时,进程会直接阻塞挂住,也会表现为无输出、不退出的现象,你之前遇到的无输出问题也有这个因素的影响。
macOS和Ubuntu下复现相同问题是正常的,这个坑和操作系统无关,是Java Runtime.exec的通用行为。
内容的提问来源于stack exchange,提问作者OpenSource

