为ProcessBuilder设置环境
我从Java(1.6)设置Linux环境有一个奇怪的问题; 特别是“PATH”变量。
简而言之,我有一个用于运行本机进程的管道,它使用java.lang.ProcessBuilder
。 用户可以选择通过HashMap
命名environment
设置环境变量:
ProcessBuilder pb = new ProcessBuilder(args); Map env = pb.environment(); if (environment != null) env.putAll(environment); Process process = pb.start();
如果我将它转储到控制台,并且PATH变量的值正确,则env
变量设置正确。 但是,运行该进程会导致抛出Exception
:
java.io.IOException: error=2, No such file or directory
在终端shell中使用相同的环境变量运行相同的进程。 为了测试这个,我在终端后设置环境后运行Eclipse。 在这种情况下, ProcessBuilder
进程正确运行。
所以必须发生的是ProcessBuilder
没有使用我为它设置的环境,而是使用当前的System环境。
我在网上找不到任何满意的答案。 也许这是一个特定于操作系统的问题? 或者其他我错过的东西?
我不认为这是一个错误,我认为你理解环境变量的边界和角色是一个问题。 ProcessBuilder.environment()
包含将对生成的进程“进程本地”的环境变量。 它们不是系统范围的,也不是登录范围的,它们甚至不会影响运行ProcessBuilder的环境。
ProcessBuilder.environment()
映射包含仅由生成的进程看到的进程局部变量。 显然,看到ProcessBuilder.environment()
的衍生处理的先决条件是成功生成过程,这是我认为你甚至没有达到的一点。
据我所知,(从Java)修改当前正在运行的进程’PATH是不可能的,这是我认为你期望发生的(或者能够做到的)。所以我认为你必须指出ProcessBuilder到您尝试启动的可执行文件的完全限定路径(或者确定在您启动将使用ProcessBuilder的JVM之前已正确设置PATH,这是您在’工作’方案中所做的在启动IDE之前将其设置在终端中。
在Linux上:
String path = System.getenv("HOME"); ProcessBuilder pb = new ProcessBuilder("/bin/bash","-c","export PATH=" + "PATH-TO-ADD" + ":" + path + " && exec");
在这种情况下, PATH
变量根据需要进行更新,并在新的$PATH
搜索可执行文件。 这在Linux上适用于我。
您需要了解环境变量是过程上下文的本地变量。 新进程获取父级环境的副本,但每个副本都是独立的。 父项的更改不会影响现有子项(仅限新项),子项中的更改不会影响父项的父项或新项。
在您的情况下,Java进程创建子进程并将修改后的PATH
变量放入子进程的上下文中。 这不会影响Java进程。 子进程不是shell,因此忽略了PATH
变量。 该过程直接使用OS服务创建。 除非在启动Java进程之前更改shell中的环境,否则它们会查看包含旧PATH
变量的Java进程的上下文。
要解决您的问题,您有两种选择:
-
检查Java中的
PATH
变量,将其拆分为路径元素并手动搜索可执行文件。 然后,您可以使用绝对路径调用ProcessBuilder
并将新PATH
放入子项中,这样孙子将拥有正确的路径。 -
调用shell以启动子进程。 shell将使用它的路径(您可以通过环境传递)。
第二种情况是这样的:
- 您使用正确的
PATH
创建环境。 - 你启动一个shell进程。
- 您将命令作为参数运行到shell(
"sh", "-c", "cmd args"
或"cmd.exe", "/c", "cmd args"
) - shell会注意到它必须运行命令
- 它将调查它的环境(您在步骤#1中配置),找到修改后的
PATH
并运行正确的命令。
第二种情况的缺点是你必须正确地转义和/或引用命令的参数( args
),或者空格和其他特殊字符会引起问题。
我想你是正确的。 当前正在执行的java代码将不使用您正在为正在执行的子进程准备的环境变量。 您可以创建一个中间可执行文件或脚本,您可以将变量传递给它并让它执行您的程序。
从ProcessBuilder javadoc可以清楚地看出,您可以使用environment()方法获取环境变量,然后修改返回的映射。 从ProcessBuilder实例启动的任何后续进程都将进行更改。
这似乎是java和外部进程的真正问题
关于Windows 7和java 7(32位)的以下内容
ProcessBuilder b = new ProcessBuilder(); Map env = b.environment(); for (String key : env.keySet()) System.out.println(key + ": " + env.get(key));
产生
SystemRoot: C:\Windows Path: xbox
这意味着正在运行的程序环境和子进程环境应该包含一个路径变量,它具有正确的值’xbox’ (例如废话,我的电脑上没有任何名为xbox的目录)
仅用于协议:
Map env = System.getenv(); for (String key : env.keySet()) System.out.println(key + ": " + env.get(key));
给出完全相同的结果。
我跑的时候
b.command("convert.exe", "/?").inheritIO().start();
我得到了这个流程构建器和环境
Konvertiert FAT-Volumes in NTFS. CONVERT Volume /FS:NTFS [/V] [/CvtArea:Dateiname] [/NoSecurity] [/X] Volume Bestimmt den Laufwerkbuchstaben (gefolgt von einem Doppelpunkt), den Bereitstellungspunkt oder das Volume. /FS:NTFS Bestimmt das in NTFS zu konvertierende Volume. /V Legt fest, dass CONVERT im ausf hrlichen Modus ausgef hrt wird. /CvtArea:Dateiname Bestimmt die zusammenh ngende Datei im Stammverzeichnis, die als Platzhalter f r NTFS-Systemdateien dienen soll. /NoSecurity Bestimmt die Sicherheitseinstellungen f r konvertierte Dateien und Verzeichnisse, die f r jeden Benutzer zug nglich sind. /X Erzwingt ggf. das Aufheben der Bereitstellung. Alle ge ffneten Handles auf das Volume sind in diesem Fall ung ltig.
这是(德国)的输出
C:\Windows\System32\convert.exe
我使用时也是如此
Runtime.getRuntime().exec(new String[]{"convert.exe", "/?"});
请注意,我的环境非常小,因为我取代了原生环境。 这意味着整个程序正好具有这两个环境变量。