How do I set environment variables from Java?
(Is it because this is Java and therefore I shouldn't be doing evil nonportable obsolete things like touching my environment?)
I think you've hit the nail on the head.
A possible way to ease the burden would be to factor out a method
void setUpEnvironment(ProcessBuilder builder) {
Map<String, String> env = builder.environment();
// blah blah
}
and pass any ProcessBuilder
s through it before starting them.
Also, you probably already know this, but you can start more than one process with the same ProcessBuilder
. So if your subprocesses are the same, you don't need to do this setup over and over.
For use in scenarios where you need to set specific environment values for unit tests, you might find the following hack useful. It will change the environment variables throughout the JVM (so make sure you reset any changes after your test), but will not alter your system environment.
I found that a combination of the two dirty hacks by Edward Campbell and anonymous works best, as one does not work under linux, and the other does not work under windows 7. So to get a multiplatform evil hack I combined them:
protected static void setEnv(Map<String, String> newenv) throws Exception {
try {
Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
theEnvironmentField.setAccessible(true);
Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
env.putAll(newenv);
Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
theCaseInsensitiveEnvironmentField.setAccessible(true);
Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
cienv.putAll(newenv);
} catch (NoSuchFieldException e) {
Class[] classes = Collections.class.getDeclaredClasses();
Map<String, String> env = System.getenv();
for(Class cl : classes) {
if("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
Field field = cl.getDeclaredField("m");
field.setAccessible(true);
Object obj = field.get(env);
Map<String, String> map = (Map<String, String>) obj;
map.clear();
map.putAll(newenv);
}
}
}
}
This Works like a charm. Full credits to the two authors of these hacks.
public static void set(Map<String, String> newenv) throws Exception {
Class[] classes = Collections.class.getDeclaredClasses();
Map<String, String> env = System.getenv();
for(Class cl : classes) {
if("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
Field field = cl.getDeclaredField("m");
field.setAccessible(true);
Object obj = field.get(env);
Map<String, String> map = (Map<String, String>) obj;
map.clear();
map.putAll(newenv);
}
}
}
Or to add/update a single var and removing the loop as per thejoshwolfe's suggestion.
@SuppressWarnings({ "unchecked" })
public static void updateEnv(String name, String val) throws ReflectiveOperationException {
Map<String, String> env = System.getenv();
Field field = env.getClass().getDeclaredField("m");
field.setAccessible(true);
((Map<String, String>) field.get(env)).put(name, val);
}
Setting single environment variables (based on answer by Edward Campbell):
public static void setEnv(String key, String value) {
try {
Map<String, String> env = System.getenv();
Class<?> cl = env.getClass();
Field field = cl.getDeclaredField("m");
field.setAccessible(true);
Map<String, String> writableEnv = (Map<String, String>) field.get(env);
writableEnv.put(key, value);
} catch (Exception e) {
throw new IllegalStateException("Failed to set environment variable", e);
}
}
Usage:
First, put the method in any class you want, e.g. SystemUtil. Then call it statically:
SystemUtil.setEnv("SHELL", "/bin/bash");
If you call System.getenv("SHELL")
after this, you'll get "/bin/bash"
back.