You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java中启动Docker容器并在容器内执行多条命令

在Java中启动Docker容器并执行内部多条命令的解决方案

问题根源分析

你之前的代码存在两个核心问题:

  1. 使用了docker exec -it的-t参数(分配伪终端),Java的Process很难正确处理这种交互式终端的输入输出;
  2. 调用process.waitFor()会阻塞到bash进程结束,此时再写入输出流已经无效,且后续命令实际并未进入容器内部。

方案1:一次性执行多条非交互式命令(推荐)

如果不需要持续交互,直接通过bash -c将多条命令打包成一个参数传递给docker exec,无需进入交互式bash。

import java.util.Arrays;
import java.util.List;

public class DockerExecExample {
    public static void main(String[] args) {
        try {
            // 定义要执行的多条命令,用&&连接(前一条成功才执行下一条),或用;连接(无论前一条是否成功都执行)
            String multiCommands = "ls && pwd && echo '容器内执行成功'";
            
            List<String> command = Arrays.asList(
                "sudo",
                "docker",
                "exec",
                "sawtooth-shell-default",
                "bash",
                "-c",
                multiCommands // 注意:这里必须是单独的一个参数
            );

            ProcessBuilder pb = new ProcessBuilder(command);
            pb.inheritIO(); // 将进程的输入输出重定向到当前Java进程的控制台
            Process process = pb.start();
            
            int exitCode = process.waitFor();
            if (exitCode != 0) {
                throw new RuntimeException("容器内命令执行失败,退出码:" + exitCode);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

关键注意点:

  • 必须去掉-it参数,非交互式场景不需要伪终端;
  • bash -c后面的命令串必须作为单独的一个参数传入ProcessBuilder,不能拆分,否则会被解析成多个无效命令。

方案2:交互式执行多条命令

如果需要持续输入命令(比如模拟人工操作终端),则需要单独处理输入输出流,避免阻塞。

import java.io.*;
import java.util.Arrays;
import java.util.List;

public class DockerInteractiveExec {
    public static void main(String[] args) {
        try {
            List<String> command = Arrays.asList(
                "sudo",
                "docker",
                "exec",
                "-i", // 仅保留-i参数,允许标准输入交互,去掉-t避免伪终端问题
                "sawtooth-shell-default",
                "bash"
            );

            ProcessBuilder pb = new ProcessBuilder(command);
            pb.redirectErrorStream(true); // 将错误输出合并到标准输出,方便统一读取
            Process process = pb.start();

            // 启动独立线程读取容器的输出,避免因缓冲区满导致进程阻塞
            new Thread(() -> {
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        System.out.println("容器输出:" + line);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();

            // 向容器内的bash写入命令,每条命令后加换行符(bash需要换行触发执行)
            try (OutputStreamWriter writer = new OutputStreamWriter(process.getOutputStream())) {
                writer.write("ls\n");
                writer.write("pwd\n");
                writer.write("echo '交互式命令执行'\n");
                writer.write("exit\n"); // 必须写入exit,让bash进程退出,否则waitFor会一直阻塞
                writer.flush();
            }

            int exitCode = process.waitFor();
            if (exitCode != 0) {
                throw new RuntimeException("交互式命令执行失败,退出码:" + exitCode);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

关键注意点:

  • 使用-i替代-it,避免伪终端带来的交互问题;
  • 必须单独启动线程读取输出流,否则进程会因输出缓冲区满而挂起;
  • 每条命令末尾必须加\n,bash只有收到换行才会执行命令;
  • 最后必须写入exit命令,让bash进程正常退出,否则process.waitFor()会一直阻塞。

内容的提问来源于stack exchange,提问作者Hamilton Hardy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 02:57:17