cmd.exe意外挂起,具体取决于我使用的文件所在的位置

时间:2017-04-11 08:52:13

标签: java windows batch-file cmd process

这必须是我曾经观察过的最奇怪的事情之一。请考虑以下Java程序:

import java.io.IOException;

public class StrangeError {
    public static void main(String[] args) {
        try {
            Process process = new ProcessBuilder(
                "cmd",
                "/c",
                "\"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64 && set"
            ).start();
            process.waitFor();
        } catch (IOException|InterruptedException e) {
            System.out.println(e.getMessage());
        }
    }
}

我使用javac StrangeError.java对其进行了编译,将其复制到运行Windows Server 2012 R2的服务器上,然后使用java StrangeError进行运行。

这里的事情开始变得怪异。该程序挂起,等待它产生的过程完成。这不是预期的行为,因为vcvarsall.bat脚本应该立即完成,set

所以我开始玩游戏并发现以下内容:

  • 删除set会导致vcvarsall.bat终止
  • 删除vcvarsall.bat会导致set终止
  • &&替换||会导致所有内容正确终止
  • vcvarsall.bat复制到桌面上的某个位置并更改路径会导致所有内容正确终止
  • nearly equivalent program使用相同的命令
  • 在Go中正常工作
  • 如果我在WinDbg中运行所有内容并在挂起后中断该过程,我会得到this output
  • 这似乎与MSVC2013中的vcvarsall.bat不可重现,但在Windows 10上也可与MSVC2015重现

原始程序究竟出了什么问题?如果我将整个命令(cmd /c "C:\...)复制并粘贴到Start-> Run中,它会立即启动cmd并按预期终止。

这是Java的错误吗?这是Windows的错误吗?

2 个答案:

答案 0 :(得分:6)

这是Java的错误吗?这是Windows的错误吗?

这是您代码中的错误。 :-)

默认情况下,使用ProcessBuilder对象创建的子进程将输出重定向到管道,其父结束可以使用Process.getInputStream()获取,如果您的代码没有使用它,则不会自动耗尽

由于您的代码只是调用.waitFor而没有任何排除管道的规定,因此一旦管道缓冲区溢出,它就会死锁。我相信默认缓冲区大小是4,096字节。在我的机器上,您运行的命令的输出是5,192字节,但这将根据环境块的原始内容而有所不同。 (从它的声音来看,你的环境中的输出长度是临界的,只是在极限之上,所以即使像改变VS的版本这样的小变化也会产生影响。)

根据您实际尝试做的事情,许多可能的解决方案之一是告诉Java不要管道孩子的输出:

import java.io.IOException;

public class StrangeError {
    public static void main(String[] args) {
        try {
            ProcessBuilder processb = new ProcessBuilder(
                "cmd",
                "/c",
                "\"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64 && set"
            );
            processb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
            Process process = processb.start();
            process.waitFor();
        } catch (IOException|InterruptedException e) {
            System.out.println(e.getMessage());
        }
    }
}

答案 1 :(得分:0)

无法在同一ProcessBuilder中读取标准输入和输出错误。

所以你需要创建两个ProcessBuilder

Process process1 = new ProcessBuilder(
            "cmd",
            "/c",
            "\"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\",
            "amd64");

Process process2 = new ProcessBuilder(
        "cmd",
        "/c",
        "set"); 

process1.start();
if (process1.waitFor() == 0) {
    process2.start();
    if (process2.waitFor() == 0) {
        // Successfull execution
    }
}

还有一件事:我不认为用Java(或其他语言)进行shell /批量启动是一个好习惯。也许您应该使用脚本(shell,batch,python,perl ...)来控制标准输入/输出流。