使用/不使用cmd.exe在Java中执行子进程命令行

3

我的问题涉及到在Windows中使用"cmd.exe"和"/c"执行Java子进程时的几个不理解的事情。基本上,我找不到一个好的解释说明何时以及为什么需要它们。

我的具体问题:我有一个用于执行子进程的小框架。其中一个用途是由ProcessBuilder创建的几个其他JVM的Java应用程序进行“管理”。其中一个关键要求是当子进程被卡住或托管应用程序正在终止时,必须能够杀死子进程。
问题在于,一方面,这样做:

new ProcessBuilder("java", "...").start();

导致这个的原因是:
Could not find or load main class ...

如果系统变量或目录不同(但它们并不不同)。另一方面,将它包装在cmd.exe中,如下所示:

new ProcessBuilder("cmd.exe", "/c", "java", "...").start();

这段代码是有效的,但创建了另一个cmd.exe进程,其副作用是:子JVM现在成为一个子子进程,而process.destroy();无法将其杀死(我发现这是Windows JRE中已知的错误)。

这个特定的问题是在不同的级别上处理的,因为所有这些应用程序都是我们自己的,并且我们知道它们的PID。但这是一个例子,说明cmd.exe使所有事情都工作得与众不同(或防止JVM完全工作)。所以我想知道那里到底发生了什么。

在这里,框架本身也进入了图片。它也将被我们的测试平台使用。我想提供一个API,允许通过一个参数用cmd.exe /c包装命令。但是,这个参数究竟是什么意思?用户如何决定他们是否需要cmd.exe包装?

而且,我会很感激额外的奖励:这对其他操作系统有任何影响吗?例如,在Linux中是否有类似的等效物?


cmd /c 将使用一些环境变量,如 CLASSPATH 和 PATH,而直接调用 java 则将从父进程获取环境。这可能解释了行为上的差异。找出您的进程环境和 cmd 环境之间的差异。 - JP Moresmau
应用程序需要完全相同的变量,这很奇怪。顺便说一下,java ... 命令包含所需的变量。缺少封闭的 cmd.exe 是否会导致它们被忽略?无论如何,这只是一个例子,但这就是问题所在:这个包装到底是做什么的?环境、输出、返回值等等。我还没有找到答案。 - Eugene Marin
使用命令提示符(cmd)主要的好处是变量替换和批处理文件支持(因此您可以调用批处理文件而不是可执行文件),按我的经验。 - JP Moresmau
你不需要调用批处理文件来执行它(即使该批处理文件调用 Java)。 - Eugene Marin
1
@JPMoresmau:cmd.exe实例也会从父进程获取其环境变量,因此这不应该有任何区别。我猜想这与Java 8使用符号链接从PATH上的目录到实际可执行文件有关。(这取决于ProcessBuilder在幕后做了什么。) - Harry Johnston
1个回答

3
再次遇到了这个问题,同时还发现了问题所在以及其他一些见解。
@HarryJohnston - 你说得很对,根据javadoc,子进程继承了ProcessBuilder和Runtime.getRuntime.exec(...) API的环境,因此不是这个问题(java.exe也能找到,所以PATH显然可用)。
但似乎某些命令行功能丢失了。其中包括命令行中使用的 %VARIABLE% 这种形式-除非命令以cmd.exe /c开头否则它们不会被解释。
在类找不到的情况下(可能也会导致NoClassDefFoundError),我在classpath中使用了多个变量(也许我应该贴出整个命令,但它有点长)。当我用文学路径替换它们时,一切正常。
基本上:
java -cp %classpath% Main - 不好
cmd.exe /c java -cp %classpath% Main - 好
java -cp D:\proj\bin Main - 好
所以,就如何使用cmd.exe的问题:
变量引用(如 %VARIABLE%)只由cmd.exe解释。
对于cmd.exe,您不需要将命令拆分为参数字符串数组,您可以使用一个长字符串("cmd.exe","/c","the rest ..."的数组)。
子进程将是cmd.exe而不是实际的可执行文件,因此 process.destroy() 不会杀死它。这可能是在稍后的版本中修复的错误(我使用了Java 7和8,Windows 7和Server 2012)。
文件关联仅适用于 cmd.exe,因此您不能“执行”不是程序或脚本的文件(“CreateProcess error=193,%1 不是有效的 Win32 应用程序”)。调用PATH中定义的可执行文件可以正常工作。
start 和 call 仅在 cmd.exe 下可用。
命令级别的错误行为也不同:对于cmd.exe,错误将只在输出流中,而没有它时API会抛出一个异常,这个异常可能还有略微不同的描述。
例如:
nothing 将导致 java.io.IOException: CreateProcess error=2, The system cannot find the file specified
cmd.exe /c nothing 将返回输出:“'nothing' is not recognized as an internal or external command, operable program or batch file”,并返回值为1(唯一表明出现问题的指示)。
在更深层次的“为什么”问题上,以及这是否是所有差异的完整列表,我仍然不太清楚。看起来JVM有其运行命令的方式,您可以使用cmd.exe进行“包装”,以获得额外的功能和保护。
另一个有趣的事实是,当您运行批处理时,它实际上在cmd.exe下运行(具有所有其功能)。从我的测试中,似乎只有在它没有等待任何外部内容(例如,如果卡在暂停状态下)时,process.destroy()才能杀死它,无论是否有额外的cmd.exe /c包装,但如果它运行子进程,则无法杀死它-这与第3点提到的cmd.exe限制是一致的。

好的回答,但我有一些小问题要解决。关于第二点,使用ProcessBuilder时,您不需要拆分命令,根据文档,您可以使用字符串而不是字符串列表。在Windows中,我始终建议使用单个字符串,因为底层实际上会将列表转换为单个字符串,有时转换会出错。关于第三点,我认为这不是一个错误,文档没有承诺目标进程的子进程将被杀死。 - Harry Johnston
所有这一切的原因并不特别复杂,ProcessBuilder正在使用Windows API直接创建一个新进程,而不是像cmd.exe解释命令行并为您创建一个新进程。如果直接创建进程,则无法使用任何cmd.exe功能,例如变量替换、使用|作为管道、<重定向输入、&在单行中运行多个命令等等。另一方面,您也不必太担心特殊字符,当然它也更高效。 - Harry Johnston
谢啦。#2-我不太记得有关分裂的情况,也许是我的测试弄坏了。#5-删除了.exe文件。但我成功地运行了cmd.exe /c start notepad,这启动了记事本。虽然我不知道其他特性,但现在我对cmd.exe的作用更加清楚了。再次感谢! - Eugene Marin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接