使用/不使用 cmd.exe 执行 Java 子进程命令行
Posted
技术标签:
【中文标题】使用/不使用 cmd.exe 执行 Java 子进程命令行【英文标题】:Java sub-process command line execution with/without cmd.exe 【发布时间】:2016-06-05 16:31:52 【问题描述】:我的问题包括一些关于在 Windows 中从 Java 执行子进程时使用“cmd.exe”、“/c”的问题。基本上,我找不到关于何时以及为什么需要它们的很好解释。 我的具体问题:我有一个用于子流程执行的小框架。一种用途是 Java 应用程序,它“管理”由 ProcessBuilders 创建的其他几个 JVM。关键要求之一是当子进程卡住或托管应用程序终止时,它必须能够终止子进程。 问题是,一方面,这样做:
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环境的区别。 应用程序需要完全相同的变量,所以很奇怪。顺便说一句,java ... 命令包含所需的变量。缺少封闭的 cmd.exe 是否会导致它们被忽略?无论如何,这只是一个例子,但这就是问题所在:这个包装到底做了什么?环境、输出、返回值等。我还没找到答案。 根据我的经验,使用 cmd 获得的主要好处是变量替换和批处理文件支持(因此您可以调用批处理文件而不是可执行文件)。 你不需要它来调用一个批处理文件(即使这个批处理文件调用Java)。 @JPMoresmau:cmd.exe 实例将也从父进程中获取其环境变量,因此应该没有任何区别。我的猜测是,这与 Java 8 使用从 PATH 目录到实际可执行文件的符号链接有关。 (取决于 ProcessBuilder 在后台做了什么。) 【参考方案1】:再次遇到它,发现了问题,以及一些额外的见解。
@HarryJohnston - 好点,根据 javadoc,子进程从 ProcessBuilder
和 Runtime.getRuntime.exec(...)
API 继承环境,所以这不是问题(实际上找到了 java.exe,因此 PATH 显然可用) .
但似乎某些命令行功能丢失了。其中,这种 %VARIABLE% 在命令行中的用法 - 除非命令以 cmd.exe /c
开头,否则它们不会被解释。
在这种找不到类的情况下(它也可能导致 NoClassDefFoundError),我在类路径中使用了几个变量(也许我应该发布整个命令,但它有点长)。当我用文学路径替换它们时,一切正常。
基本上:
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”、“其余...”的数组) )。
子进程将是 cmd.exe 而不是实际的可执行文件,所以
process.destroy()
不会杀死它。这可能是在更高版本中修复的错误(我使用了 Java 7 和 8、Windows 7 和 Server 2012)。
文件关联仅适用于 cmd.exe,因此您不能“执行”不是程序或脚本的文件(“CreateProcess 错误=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' 不被识别为内部或外部命令、可运行程序或批处理文件,返回值 1(唯一指示出现问题) .
关于为什么这个更深层次的问题,以及这是差异的完整列表,我仍然不知道。看起来 JVM 有其运行命令的方式,您可以使用 cmd.exe “包装”它以获得额外的功能和保护。
另一个有趣的事实是,当您运行 Batch 时,它实际上是在 cmd.exe(及其所有功能)下运行的。从我的测试来看,process.destroy()
似乎只能在它不等待任何外部的情况下(例如,如果卡在pause
上),有或没有额外的 cmd.exe /c 包装,但如果它运行子进程则不能杀死它- 这与第 3 点中提到的 cmd.exe 限制一致。
【讨论】:
很好的答案,但我确实有一些缺点要选择。 Re 2. 在使用 ProcessBuilder 时,您也不需要拆分命令,根据文档,您可以使用字符串而不是字符串列表。在 Windows 中,我总是建议使用单个字符串,因为这实际上是在幕后发生的 - 列表将在执行之前转换为单个字符串,有时转换是错误的。 Re 3. 我不认为这是一个错误,文档并没有保证目标进程的子进程会被杀死。 为什么所有这一切都不是特别复杂,ProcessBuilder 是使用 Windows API 直接创建一个新进程,这与让cmd.exe
解释命令行并创建完全不同一个新的过程给你。如果您直接创建进程,则没有cmd.exe
功能可用,例如,变量替换、使用|
作为管道、<
重定向输入、&
在一行中运行多个命令等等。另一方面,您也不必担心特殊字符,当然它的效率要高得多。
谢谢。 #2 - 真的不记得分裂了,也许我的测试 fd 出了点问题。 #5 - 删除 .exe。但我确实设法运行了启动记事本的cmd.exe /c start notepad
。不知道其他功能,但是 cmd.exe 的意义现在更清楚了。再次感谢!以上是关于使用/不使用 cmd.exe 执行 Java 子进程命令行的主要内容,如果未能解决你的问题,请参考以下文章