Setting the environment for ProcessBuilder

2020-02-01 07:22发布

I have a strange problem setting the Linux environment from Java (1.6); specifically the "PATH" variable.

In a nutshell, I have a pipeline for running native processes, which uses java.lang.ProcessBuilder. The user can optionally set the environment variables via a HashMap named environment:

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

The env variable gets set properly, if I dump it to the console, with a correct value for the PATH variable. However, running the process results in a thrown Exception:

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

The same process runs fine with identical environment variables in the terminal shell. To test this, I ran Eclipse AFTER setting the environment in the terminal. In this case the ProcessBuilder process runs correctly.

So what must be happening is that the ProcessBuilder is not using the environment I set for it, but the current System environment instead.

I can't find any satisfactory answers to this problem online. Perhaps this is an OS-specific issue? Or something else I'm missing?

6条回答
够拽才男人
2楼-- · 2020-02-01 07:44

I don't think it's a bug, I think it's a problem with your understanding of the boundaries and roles of the environment variables at play. ProcessBuilder.environment() contains environment variables that will be "process-local" to the spawned process. They are not system-wide, or logon-wide, and they don't even effect the environment in which the ProcessBuilder is running.

The ProcessBuilder.environment() map contains process-local variables that will be seen only by the spawned process. Obviously a prerequisite to the spawned processing seeing the ProcessBuilder.environment() is successful spawning of the process, which is a point I do not think you're even getting to.

As far as I know, it's not really possible (from Java) to modify the currently-running process' PATH, which is what I think you're expecting to happen (or to be able to do.) So I think you must point ProcessBuilder to the fully qualified path to the executable you're trying to launch (or be certain that the PATH was set up correctly before you even launch the JVM that will use the ProcessBuilder, which is what you did in your 'working' scenario of setting it in the terminal before launching your IDE).

查看更多
Bombasti
3楼-- · 2020-02-01 07:52

This seems to be a real issue with java and external processes

the following on windows 7 and java 7 (32bit)

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

produces

SystemRoot: C:\Windows
Path: xbox

which means the running programs environment and the subprocesses environment should contain a path variable, that has exactly the value 'xbox' (e.g. nonsense, there is no directory named xbox anywhere on my pc)

just for protocol:

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

gives exactly the same result.

when I run

b.command("convert.exe", "/?").inheritIO().start();

with this process builder and environment I get

    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.

this is the (german) output of

C:\Windows\System32\convert.exe

The same happens when I use

Runtime.getRuntime().exec(new String[]{"convert.exe", "/?"});

And note that my environment is so small because I replaced the native enviroment. That means the whole program has exactly those two environment variables.

查看更多
Evening l夕情丶
4楼-- · 2020-02-01 07:55

One thing that is clear from the ProcessBuilder javadoc is that you can get the environment variables with the environment() method, and then modify the returned map. Any subsequent process that is launched from that ProcessBuilder instance will have your changes.

查看更多
The star\"
5楼-- · 2020-02-01 07:56

On Linux:

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

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

In this case the PATH variable is updated as per the need and the executable is searched in new $PATH. This worked for me on Linux.

查看更多
Luminary・发光体
6楼-- · 2020-02-01 07:58

I think you're right. The currently executing java code will not use the environment variables you are preparing for the child process you are executing. You could create an intermediate executable or script that you can pass variables to and have it execute your program.

查看更多
兄弟一词,经得起流年.
7楼-- · 2020-02-01 08:05

You need to understand that environment variables are local to process contexts. A new process gets a copy of the parent's environment but each copy is independent. Changes in the parent don't affect existing children (only new ones) and changes in children don't affect the parent or new children of the parent.

In your case, the Java process creates child process and puts a modified PATH variable into the child's context. This doesn't affect the Java process. The child process isn't a shell, so it ignores the PATH variable. The process is created directly using OS services. Those look into the context of the Java process which contains the old PATH variable unless you change the environment in the shell before you start the Java process.

To fix your issue, you have two choices:

  1. Examine the PATH variable in Java, split it into path elements and search for the executable manually. You can then invoke ProcessBuilder with the absolute path and put the new PATH into the child, so grandchildren will have the correct path.

  2. Invoke a shell to start the child process. The shell will use it's path (which you can pass via the environment).

The second case works like this:

  1. You create an environment with the correct PATH.
  2. You start a shell process.
  3. You pass the command to run as argument to the shell ("sh", "-c", "cmd args" or "cmd.exe", "/c", "cmd args")
  4. The shell will notice that it has to run a command
  5. It will look into it's environment (which you configured in step #1), find the modified PATH and run the correct command.

The drawback of the second case is that you have to properly escape and/or quote the arguments for the command (args), or spaces and other special characters will cause problems.

查看更多
登录 后发表回答