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 proce
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:
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.
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:
PATH
."sh", "-c", "cmd args"
or "cmd.exe", "/c", "cmd args"
)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.
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.
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.
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 affect 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).
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.
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.