为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进程的上下文。

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

  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 ),或者空格和其他特殊字符会引起问题。

我想你是正确的。 当前正在执行的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", "/?"}); 

请注意,我的环境非常小,因为我取代了原生环境。 这意味着整个程序正好具有这两个环境变量。