为ProcessBuilder设置环境

13

我有一个奇怪的问题,使用Java(1.6)从Linux环境中设置"PATH"变量。

简单说,我有一个管道用于运行本机进程,它使用java.lang.ProcessBuilder。用户可以选择通过名为environmentHashMap设置环境变量:

ProcessBuilder pb = new ProcessBuilder(args);
Map<String, String> env = pb.environment();
if (environment != null)
   env.putAll(environment);
Process process = pb.start();
如果我将env打印到控制台,它会正确地设置,并具有PATH变量的正确值。然而,运行该进程会导致抛出异常:java.io.IOException: error=2, No such file or directory
在终端 shell 中使用相同的环境变量运行相同的进程时,结果是正常的。为了测试这一点,在终端中设置环境后,我运行了 Eclipse。在这种情况下,ProcessBuilder 进程可以正常运行。
因此,可能发生的情况是 ProcessBuilder 没有使用我设置的环境,而是使用当前系统环境。
我找不到任何满意的在线解答。也许这是一个特定于操作系统的问题?还是我遗漏了什么?

也许尝试使用sysout输出PATH变量并在此处发布,也许我们可以发现一些东西.. - barsju
6
在Windows中,还有一件需要注意的事情是环境变量名的大小写通常不重要(引用自System.getenv javadoc),这意味着您可以使用类似的结果交替调用System.getenv("path")System.getenv("PATH")。但是对于ProcessBuilder的环境字段来说却并非如此,因为该映射是直接访问而不是通过特定的getter访问,所以任何大小写上的差异都会导致错误...提示:如果您确实想在Windows中获取ProcessBuilder的路径,请使用environment.get("Path") - PeterT
@PeterT 你给出的提示,建议在Windows上使用Path,真是太棒了!!!我因为ProcessBuilder的这个问题已经花费了好几周的时间!!非常感谢!!!:) :) - Destructor
@Destructor 很高兴能帮忙...我记不清它困扰了我多久:不是几周,但足够长时间让人感到沮丧 :) - PeterT
6个回答

17

我并不认为这是个bug,而是你对所涉及的环境变量的边界和角色的理解问题。 ProcessBuilder.environment() 包含将“仅进程本地”传递给已生成进程的环境变量。 它们不是系统范围的、登录范围的,甚至不影响 ProcessBuilder 运行的环境。

ProcessBuilder.environment() 映射包含进程本地变量,此变量仅由生成的进程看到。显然,生成处理程序成功看到 ProcessBuilder.environment() 是前提条件,并且我认为您甚至没有达到此点。

据我所知,(从Java)无法修改当前运行进程的PATH,这也是您期望发生(或能够执行)的事情。 因此,我认为您必须将 ProcessBuilder 指向您要启动的可执行文件的完全限定路径(或者在启动将使用 ProcessBuilder 的 JVM 之前确保 PATH 正确定位,这就是您在终端设置路径之后启动 IDE 中的 “工作”场景所做的事情)。


谢谢,这非常有帮助。假设我指定了目标进程的完整路径,那么这是否意味着它将使用ProcessBuilder的进程本地环境变量,如果它自己调用其他进程?这对我来说是一个合适的解决方案。 - Andrew Reid
我不确定我完全理解(特别是关于“调用其他进程”方面),但我相当肯定答案是肯定的。 - Mike Clark
我明白你的意思。你能指向那些说明相同的文档吗? - Destructor

10
你需要明白环境变量是与进程上下文相关联的。新进程会获取父进程的环境副本,但每个副本都是独立的。对父进程的更改不会影响现有子进程(只会影响新的子进程),而对子进程的更改也不会影响父进程或者父级的新子进程。
在你的情况下,Java进程创建一个子进程并将修改后的`PATH`变量放入子进程的上下文中。这不会影响Java进程。子进程不是shell,所以它会忽略`PATH`变量。该进程是直接使用操作系统服务创建的。这些服务查看包含旧的`PATH`变量的Java进程上下文,除非在启动Java进程之前在shell中更改了环境变量。
要解决您的问题,有两个选择: 1. 在Java中检查`PATH`变量,将其拆分为路径元素并手动搜索可执行文件。然后你可以使用绝对路径调用ProcessBuilder和将新的`PATH`放入子级中,以便孙子级将具有正确的路径。 2. 调用shell以启动子进程。 shell将使用它的路径(可以通过环境传递)。
第二种情况的工作原理如下: 1. 创建带有正确`PATH`的环境。 2. 启动shell进程。 3. 将要运行的命令作为参数传递给shell("sh", "-c", "cmd args"或"cmd.exe", "/c", "cmd args")。 4. shell将注意到它必须运行一个命令。 5. 它将查找它的环境(在步骤#1中配置),找到修改后的`PATH`并运行正确的命令。
第二种情况的缺点是必须正确转义和/或引用命令参数(`args`),否则空格和其他特殊字符会导致问题。

8

在 Linux 上:

String path = System.getenv("HOME");

ProcessBuilder pb = new ProcessBuilder("/bin/bash","-c","export PATH=" +
    "PATH-TO-ADD" + ":" + path + " && exec");

在这种情况下,根据需要更新PATH变量,并在新的$PATH中搜索可执行文件。这在Linux上对我有效。

或者运行一个 sh 文件并编辑它,在执行该 sh 脚本之前设置你需要的变量。 - tgkprog

2
从ProcessBuilder javadoc中可以清楚地看到,您可以使用environment()方法获取环境变量,然后修改返回的映射。任何从该ProcessBuilder实例启动的后续进程都将具有您所做的更改。

0

我认为你是对的。当前正在执行的Java代码不会使用您为执行的子进程准备的环境变量。您可以创建一个中间可执行文件或脚本,将变量传递给它并让它执行您的程序。


0

这似乎是 Java 和外部进程之间的真正问题

在 Windows 7 和 Java 7(32位)上如下:

ProcessBuilder b = new ProcessBuilder();
Map<String, String> env = b.environment();
for (String key : env.keySet())
     System.out.println(key + ": " + env.get(key));

生成

SystemRoot: C:\Windows
Path: xbox

这意味着运行程序的环境和子进程的环境应该包含一个路径变量,其值恰好为“xbox”(例如,胡说八道,在我的电脑上没有名为xbox的目录)

仅供参考:

Map<String, String> 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", "/?"});

请注意,我的环境非常小,因为我替换了本机环境。这意味着整个程序仅具有这两个环境变量。

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