14

我从 Java (1.6) 设置 Linux 环境时遇到一个奇怪的问题;特别是“PATH”变量。

简而言之,我有一个用于运行本机进程的管道,它使用java.lang.ProcessBuilder. 用户可以选择通过HashMap命名设置环境变量environment

ProcessBuilder pb = new ProcessBuilder(args);
Map<String, String> env = pb.environment();
if (environment != null)
   env.putAll(environment);
Process process = pb.start();

env如果我将变量转储到控制台,则该变量会正确设置,并为 PATH 变量设置正确的值。但是,运行该过程会导致抛出Exception

java.io.IOException: error=2, No such file or directory

相同的进程在终端 shell 中使用相同的环境变量运行良好。为了测试这一点,我在终端中设置环境后运行了 Eclipse。在这种情况下,该ProcessBuilder过程正常运行。

所以必须发生的是,ProcessBuilder它没有使用我为它设置的环境,而是使用当前的系统环境。

我在网上找不到这个问题的任何令人满意的答案。也许这是一个特定于操作系统的问题?还是我缺少的其他东西?

4

6 回答 6

16

我不认为这是一个错误,我认为这是您对环境变量的边界和作用的理解的问题。 ProcessBuilder.environment()包含对生成的进程来说是“进程本地”的环境变量。它们不是系统范围的,也不是登录范围的,它们甚至不影响 ProcessBuilder 运行的环境。

ProcessBuilder.environment()映射包含进程局部变量,只有生成的进程才能看到。显然,看到生成处理的先决条件ProcessBuilder.environment()是成功生成进程,这是我认为你甚至没有达到的一点。

据我所知,(从 Java 中)修改当前正在运行的进程的 PATH 是不可能的,这是我认为你期望发生的(或能够做到的)。所以我认为你必须指出ProcessBuilder 到您尝试启动的可执行文件的完全限定路径(或者在您启动将使用 ProcessBuilder 的 JVM 之前确保 PATH 设置正确,这是您在“工作”场景中所做的在启动 IDE 之前在终端中设置它)。

于 2012-04-05T21:37:37.117 回答
8

在 Linux 上:

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

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

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

于 2012-05-22T16:55:15.603 回答
8

您需要了解环境变量对于进程上下文是本地的。一个新进程获取父环境的副本,但每个副本都是独立的。父项的更改不会影响现有子项(仅新子项),子项的更改不会影响父项或父项的新子

在您的情况下,Java 进程创建子进程并将修改后的PATH变量放入子进程的上下文中。这不会影响 Java 进程。子进程不是 shell,因此它忽略了PATH变量。该进程是直接使用操作系统服务创建的。除非您在启动 Java 进程之前PATH更改 shell 中的环境,否则它们会查看包含旧变量的 Java 进程的上下文。

要解决您的问题,您有两种选择:

  1. 检查PATHJava 中的变量,将其拆分为路径元素并手动搜索可执行文件。然后,您可以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) 的参数,否则空格和其他特殊字符会导致问题。

于 2015-12-03T08:34:05.057 回答
2

ProcessBuilder javadoc 中清楚的一件事是,您可以使用 environment() 方法获取环境变量,然后修改返回的映射。从该 ProcessBuilder 实例启动的任何后续流程都将包含您的更改。

于 2012-11-30T01:53:31.923 回答
0

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

于 2012-04-05T21:18:18.390 回答
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", "/?"});

请注意,我的环境是如此之小,因为我替换了原生环境。这意味着整个程序恰好具有这两个环境变量。

于 2013-10-24T07:47:36.050 回答