如何从Java设置环境variables?

如何从Java设置环境variables? 我看到我可以使用ProcessBuilder为subprocess执行此操作。 不过,我有几个subprocess可以启动,所以我宁愿修改当前进程的环境,让subprocessinheritance它。

有一个System.getenv(string)获取单个环境variables。 我也可以通过System.getenv()获得一组完整的环境variables的Map。 但是在这个Map上调用put()会抛出一个UnsupportedOperationException – 显然这意味着环境只能被读取。 并没有System.setenv()。

那么,有什么办法可以在当前正在运行的进程中设置环境variables? 如果是这样,怎么样? 如果不是,理由是什么? (这是因为这是Java,所以我不应该做像触摸我的环境这样邪恶的不可移植的过时的东西?)如果不是,pipe理环境variables的任何好的build议改变,我将需要喂养几个subprocess?

(这是因为这是Java,所以我不应该像触摸我的环境那样做坏的不可移植的过时的东西?)

我认为你已经击中了头部。

减轻负担的一种可能的方法是分解一种方法

void setUpEnvironment(ProcessBuilder builder) { Map<String, String> env = builder.environment(); // blah blah } 

并在启动任何ProcessBuilder之前通过它。

另外,您可能已经知道这一点,但您可以使用相同的ProcessBuilder启动多个进程。 所以如果你的subprocess是一样的,你不需要一遍又一遍的做这个设置。

为了在需要为unit testing设置特定环境值的场景中使用,您可能会发现以下hack有用。 它会在整个JVM中改变环境variables(所以确保你在testing之后重置所有的改变),但是不会改变你的系统环境。

我发现爱德华·坎贝尔和匿名两个肮脏的黑客组合最好的作品,因为其中一个不能在Linux下工作,一个不能在Windows 7下工作。所以要得到一个多平台的邪恶黑客我结合他们:

 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); } } } } 

这工作就像一个魅力。 这些黑客的两位作者完全得分。

 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); } } } 

在Android上,接口通过Libcore.os作为一种隐藏的API公开。

 Libcore.os.setenv("VAR", "value", bOverwrite); Libcore.os.getenv("VAR")); 

Libcore类以及接口操作系统是公共的。 只是类声明丢失,需要显示给链接器。 不需要将类添加到应用程序,但是如果包含它也不会受到伤害。

 package libcore.io; public final class Libcore { private Libcore() { } public static Os os; } package libcore.io; public interface Os { public String getenv(String name); public void setenv(String name, String value, boolean overwrite) throws ErrnoException; } 
 // this is a dirty hack - but should be ok for a unittest. private void setNewEnvironmentHack(Map<String, String> newenv) throws Exception { 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.clear(); env.putAll(newenv); Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment"); theCaseInsensitiveEnvironmentField.setAccessible(true); Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null); cienv.clear(); cienv.putAll(newenv); } 

事实certificate,来自@ pushy / @ anonymous / @ Edward Campbell的解决scheme不适用于Android,因为Android并不是真正的Java。 具体来说,Android根本没有java.lang.ProcessEnvironment 。 但事实certificate,在Android中更容易,只需要对POSIX setenv()进行JNI调用:

在C / JNI中:

 JNIEXPORT jint JNICALL Java_com_example_posixtest_Posix_setenv (JNIEnv* env, jclass clazz, jstring key, jstring value, jboolean overwrite) { char* k = (char *) (*env)->GetStringUTFChars(env, key, NULL); char* v = (char *) (*env)->GetStringUTFChars(env, value, NULL); int err = setenv(k, v, overwrite); (*env)->ReleaseStringUTFChars(env, key, k); (*env)->ReleaseStringUTFChars(env, value, v); return err; } 

在Java中:

 public class Posix { public static native int setenv(String key, String value, boolean overwrite); private void runTest() { Posix.setenv("LD_LIBRARY_PATH", "foo", true); } } 

在网上讨论,看起来可能可以用JNI来做到这一点。 然后你必须从C中调用putenv(),并且你(可能)必须以在Windows和UNIX上都能工作的方式来完成。

如果所有这些都能做到的话,那么Java本身就不会太难支持这一点,而不是把我放在一件直筒夹克上。

说Perl的朋友认为这是因为环境variables是过程全球性的,Java正在努力为良好的devise进行良好的隔离。

这是@ paul-blair的答案转化为Java的结合,其中包括保罗·布莱尔指出的一些清理工作,以及一些由@ Edward Campbell和匿名组成的@pushy代码中的错误。

我不能强调这个代码只能用于testing,而且非常黑客。 但是对于需要testing环境设置的情况,这正是我所需要的。

这也包括我的一些小的触动,允许代码在两个Windows上运行的工作

 java version "1.8.0_92" Java(TM) SE Runtime Environment (build 1.8.0_92-b14) Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode) 

以及运行的Centos

 openjdk version "1.8.0_91" OpenJDK Runtime Environment (build 1.8.0_91-b14) OpenJDK 64-Bit Server VM (build 25.91-b14, mixed mode) 

执行:

 /** * Sets an environment variable FOR THE CURRENT RUN OF THE JVM * Does not actually modify the system's environment variables, * but rather only the copy of the variables that java has taken, * and hence should only be used for testing purposes! * @param key The Name of the variable to set * @param value The value of the variable to set */ @SuppressWarnings("unchecked") public static <K,V> void setenv(final String key, final String value) { try { /// we obtain the actual environment final Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment"); final Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment"); final boolean environmentAccessibility = theEnvironmentField.isAccessible(); theEnvironmentField.setAccessible(true); final Map<K,V> env = (Map<K, V>) theEnvironmentField.get(null); if (SystemUtils.IS_OS_WINDOWS) { // This is all that is needed on windows running java jdk 1.8.0_92 if (value == null) { env.remove(key); } else { env.put((K) key, (V) value); } } else { // This is triggered to work on openjdk 1.8.0_91 // The ProcessEnvironment$Variable is the key of the map final Class<K> variableClass = (Class<K>) Class.forName("java.lang.ProcessEnvironment$Variable"); final Method convertToVariable = variableClass.getMethod("valueOf", String.class); final boolean conversionVariableAccessibility = convertToVariable.isAccessible(); convertToVariable.setAccessible(true); // The ProcessEnvironment$Value is the value fo the map final Class<V> valueClass = (Class<V>) Class.forName("java.lang.ProcessEnvironment$Value"); final Method convertToValue = valueClass.getMethod("valueOf", String.class); final boolean conversionValueAccessibility = convertToValue.isAccessible(); convertToValue.setAccessible(true); if (value == null) { env.remove(convertToVariable.invoke(null, key)); } else { // we place the new value inside the map after conversion so as to // avoid class cast exceptions when rerunning this code env.put((K) convertToVariable.invoke(null, key), (V) convertToValue.invoke(null, value)); // reset accessibility to what they were convertToValue.setAccessible(conversionValueAccessibility); convertToVariable.setAccessible(conversionVariableAccessibility); } } // reset environment accessibility theEnvironmentField.setAccessible(environmentAccessibility); // we apply the same to the case insensitive environment final Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment"); final boolean insensitiveAccessibility = theCaseInsensitiveEnvironmentField.isAccessible(); theCaseInsensitiveEnvironmentField.setAccessible(true); // Not entirely sure if this needs to be casted to ProcessEnvironment$Variable and $Value as well final Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null); if (value == null) { // remove if null cienv.remove(key); } else { cienv.put(key, value); } theCaseInsensitiveEnvironmentField.setAccessible(insensitiveAccessibility); } catch (final ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { throw new IllegalStateException("Failed setting environment variable <"+key+"> to <"+value+">", e); } catch (final NoSuchFieldException e) { // we could not find theEnvironment final Map<String, String> env = System.getenv(); Stream.of(Collections.class.getDeclaredClasses()) // obtain the declared classes of type $UnmodifiableMap .filter(c1 -> "java.util.Collections$UnmodifiableMap".equals(c1.getName())) .map(c1 -> { try { return c1.getDeclaredField("m"); } catch (final NoSuchFieldException e1) { throw new IllegalStateException("Failed setting environment variable <"+key+"> to <"+value+"> when locating in-class memory map of environment", e1); } }) .forEach(field -> { try { final boolean fieldAccessibility = field.isAccessible(); field.setAccessible(true); // we obtain the environment final Map<String, String> map = (Map<String, String>) field.get(env); if (value == null) { // remove if null map.remove(key); } else { map.put(key, value); } // reset accessibility field.setAccessible(fieldAccessibility); } catch (final ConcurrentModificationException e1) { // This may happen if we keep backups of the environment before calling this method // as the map that we kept as a backup may be picked up inside this block. // So we simply skip this attempt and continue adjusting the other maps // To avoid this one should always keep individual keys/value backups not the entire map LOGGER.info("Attempted to modify source map: "+field.getDeclaringClass()+"#"+field.getName(), e1); } catch (final IllegalAccessException e1) { throw new IllegalStateException("Failed setting environment variable <"+key+"> to <"+value+">. Unable to access field!", e1); } }); } LOGGER.info("Set environment variable <"+key+"> to <"+value+">. Sanity Check: "+System.getenv(key)); } 

仅限Linux

设置单个环境variables(根据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); } } 

用法:

首先,将该方法放在你想要的任何类中,例如SystemUtil。

 SystemUtil.setEnv("SHELL", "/bin/bash"); 

如果在此之后调用System.getenv("SHELL") ,则会返回"/bin/bash"

尝试了上面的pushy的答案,它的大部分工作。 但是,在某些情况下,我会看到这个例外:

 java.lang.String cannot be cast to java.lang.ProcessEnvironment$Variable 

事实certificate,当方法被多次调用时,由于ProcessEnvironment.的某些内部类的实现而发生ProcessEnvironment. 如果setEnv(..)调用setEnv(..)方法,那么当从theEnvironment映射中检索键时,它们现在是string(通过第一次调用setEnv(...) )被放入string中,不能转换为映射的genericstypesVariable,它是ProcessEnvironment.的私有内部类ProcessEnvironment.

下面是一个固定版本(在Scala中)。 希望将其转换成Java并不困难。

 def setEnv(newenv: java.util.Map[String, String]): Unit = { try { val processEnvironmentClass = JavaClass.forName("java.lang.ProcessEnvironment") val theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment") theEnvironmentField.setAccessible(true) val variableClass = JavaClass.forName("java.lang.ProcessEnvironment$Variable") val convertToVariable = variableClass.getMethod("valueOf", classOf[java.lang.String]) convertToVariable.setAccessible(true) val valueClass = JavaClass.forName("java.lang.ProcessEnvironment$Value") val convertToValue = valueClass.getMethod("valueOf", classOf[java.lang.String]) convertToValue.setAccessible(true) val sampleVariable = convertToVariable.invoke(null, "") val sampleValue = convertToValue.invoke(null, "") val env = theEnvironmentField.get(null).asInstanceOf[java.util.Map[sampleVariable.type, sampleValue.type]] newenv.foreach { case (k, v) => { val variable = convertToVariable.invoke(null, k).asInstanceOf[sampleVariable.type] val value = convertToValue.invoke(null, v).asInstanceOf[sampleValue.type] env.put(variable, value) } } val theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment") theCaseInsensitiveEnvironmentField.setAccessible(true) val cienv = theCaseInsensitiveEnvironmentField.get(null).asInstanceOf[java.util.Map[String, String]] cienv.putAll(newenv); } catch { case e : NoSuchFieldException => { try { val classes = classOf[java.util.Collections].getDeclaredClasses val env = System.getenv() classes foreach (cl => { if("java.util.Collections$UnmodifiableMap" == cl.getName) { val field = cl.getDeclaredField("m") field.setAccessible(true) val map = field.get(env).asInstanceOf[java.util.Map[String, String]] // map.clear() // Not sure why this was in the code. It means we need to set all required environment variables. map.putAll(newenv) } }) } catch { case e2: Exception => e2.printStackTrace() } } case e1: Exception => e1.printStackTrace() } } 

和大多数发现这个线程的人一样,我正在编写一些unit testing,并且需要修改环境variables来设置testing运行的正确条件。 然而,我发现最有回报的答案有一些问题和/或是非常神秘或过于复杂。 希望这能帮助其他人更快地理清解决scheme。

首先,我终于发现@Hubert Grzeskowiak的解决scheme是最简单的,它为我工作。 我希望我会先来看看那个。 这是基于@爱德华·坎贝尔的答案,但没有复杂的循环search。

不过,我从@ptyy的解决scheme开始,它获得了最多的赞誉。 这是@anonymous和@Edward Campbell的组合。 @pushy声称这两种方法都需要涵盖Linux和Windows环境。 我在OS X下运行,发现这两个工作(一旦@anonymous方法的问题是固定的)。 正如其他人所指出的,这个解决scheme大部分时间都适用,但不是全部。

我认为大部分混乱的根源来自@ anonymous在'theEnvironment'字段上运行的解决scheme。 看一下ProcessEnvironment结构的定义,'theEnvironment'不是一个Map <String,String>,而是一个Map <Variable,Value>。 清除地图工作正常,但putAll操作重build映射一个Map <String,String>,这可能会导致问题,当后续操作使用正常的API,需要Map <Variable,Value>的数据结构上操作。 而且,访问/删除单个元素也是一个问题。 解决scheme是通过“不可调整的环境”间接访问“环境”。 但是由于这是一个UnmodifiableMaptypes,访问必须通过UnmodifiableMaptypes的私有variables“m”来完成。 请参阅下面代码中的getModifiableEnvironmentMap2。

在我的情况下,我需要删除我的testing的一些环境variables(其他人应该保持不变)。 然后,我想在testing之后将环境variables恢复到之前的状态。 下面的例程使得这一点很简单。 我在OS X上testing了两个版本的getModifiableEnvironmentMap,两者都是等价的。 虽然根据评论在这个线程,一个可能是比另一个更好的select取决于环境。

注意:我没有包含对'theCaseInsensitiveEnvironmentField'的访问,因为这似乎是Windows特定的,我无法testing它,但添加它应该是直截了当的。

 private Map<String, String> getModifiableEnvironmentMap() { try { Map<String,String> unmodifiableEnv = System.getenv(); Class<?> cl = unmodifiableEnv.getClass(); Field field = cl.getDeclaredField("m"); field.setAccessible(true); Map<String,String> modifiableEnv = (Map<String,String>) field.get(unmodifiableEnv); return modifiableEnv; } catch(Exception e) { throw new RuntimeException("Unable to access writable environment variable map."); } } private Map<String, String> getModifiableEnvironmentMap2() { try { Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment"); Field theUnmodifiableEnvironmentField = processEnvironmentClass.getDeclaredField("theUnmodifiableEnvironment"); theUnmodifiableEnvironmentField.setAccessible(true); Map<String,String> theUnmodifiableEnvironment = (Map<String,String>)theUnmodifiableEnvironmentField.get(null); Class<?> theUnmodifiableEnvironmentClass = theUnmodifiableEnvironment.getClass(); Field theModifiableEnvField = theUnmodifiableEnvironmentClass.getDeclaredField("m"); theModifiableEnvField.setAccessible(true); Map<String,String> modifiableEnv = (Map<String,String>) theModifiableEnvField.get(theUnmodifiableEnvironment); return modifiableEnv; } catch(Exception e) { throw new RuntimeException("Unable to access writable environment variable map."); } } private Map<String, String> clearEnvironmentVars(String[] keys) { Map<String,String> modifiableEnv = getModifiableEnvironmentMap(); HashMap<String, String> savedVals = new HashMap<String, String>(); for(String k : keys) { String val = modifiableEnv.remove(k); if (val != null) { savedVals.put(k, val); } } return savedVals; } private void setEnvironmentVars(Map<String, String> varMap) { getModifiableEnvironmentMap().putAll(varMap); } @Test public void myTest() { String[] keys = { "key1", "key2", "key3" }; Map<String, String> savedVars = clearEnvironmentVars(keys); // do test setEnvironmentVars(savedVars); } 

您可以使用-D将parameter passing到您的初始java进程中:

 java -cp <classpath> -Dkey1=value -Dkey2=value ...