How do I set environment variables from Java? I see that I can do this for subprocesses using ProcessBuilder. I have several subprocesses to start, though, so I'd rather modify the current process's environment and let the subprocesses inherit it.
如何从Java设置环境变量?我看到可以使用ProcessBuilder对子进程执行此操作。不过,我有几个子进程要启动,所以我宁愿修改当前进程的环境,让子进程继承它。
There's a System.getenv(String) for getting a single environment variable. I can also get a Map of the complete set of environment variables with System.getenv(). But calling put() on that Map throws an UnsupportedOperationException -- apparently they mean for the environment to be read only. And there's no System.setenv().
有一个System.getenv(String)用于获取单个环境变量。我还可以使用System.getenv()获得一组完整的环境变量的映射。但是调用该图上的put()会抛出一个UnsupportedOperationException——显然,它们的意思是只读取环境。没有System.setenv()。
So, is there any way to set environment variables in the currently running process? If so, how? If not, what's the rationale? (Is it because this is Java and therefore I shouldn't be doing evil nonportable obsolete things like touching my environment?) And if not, any good suggestions for managing the environment variable changes that I'm going to need to be feeding to several subprocesses?
那么,是否有办法在当前运行的进程中设置环境变量?如果是这样,如何?如果没有,理由是什么?(因为这是Java,所以我不应该做那些邪恶的、不可移植的东西,比如触摸我的环境?)如果没有,对于管理环境变量有什么好的建议吗?我需要向几个子进程提供这些建议吗?
12 个解决方案
#1
75
(Is it because this is Java and therefore I shouldn't be doing evil nonportable obsolete things like touching my environment?)
(是因为这是Java,所以我不应该做一些邪恶的、不可移植的、过时的事情,比如触摸我的环境?)
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.
此外,您可能已经知道这一点,但是您可以使用同一个ProcessBuilder启动多个进程。如果你的子进程是一样的,你不需要一遍又一遍地做这个设置。
#2
174
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.
为了在需要为单元测试设置特定环境值的场景中使用,您可能会发现下面的方法是有用的。它将在整个JVM中更改环境变量(因此确保在测试后重置任何更改),但不会改变系统环境。
I found that a combination of the two dirty hacks by Edward Campbell and anonymous works best, as one of the does not work under linux, one does not work under windows 7. So to get a multiplatform evil hack I combined them:
我发现爱德华·坎贝尔(Edward Campbell)和“匿名者”(anonymous)这两个肮脏的黑客组合最好用,因为其中一个在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);
}
}
}
}
This Works like a charm. Full credits to the two authors of these hacks.
这很有魅力。完全归功于两位作者的这些黑客。
#3
42
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);
}
}
}
#4
16
on Android the interface is exposed via Libcore.os as a kind of hidden API.
在Android上,界面是通过Libcore公开的。os是一种隐藏的API。
Libcore.os.setenv("VAR", "value", bOverwrite);
Libcore.os.getenv("VAR"));
The Libcore class as well as the interface OS is public. Just the class declaration is missing and need to be shown to the linker. No need to add the classes to the application, but it also does not hurt if it is included.
Libcore类和接口OS都是公共的。只是缺少类声明,需要显示给链接器。不需要将类添加到应用程序中,但如果包含类,也不会造成伤害。
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;
}
#5
13
// 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);
}
#6
7
Linux only
Setting single environment variables (based on answer by Edward Campbell):
设置单个环境变量(基于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.
首先,把这个方法放到你想要的任何类中,例如SystemUtil。
SystemUtil.setEnv("SHELL", "/bin/bash");
If you call System.getenv("SHELL")
after this, you'll get "/bin/bash"
back.
如果您调用System.getenv(“SHELL”)之后,您将返回“/bin/bash”。
#7
6
It turns out that the solution from @pushy/@anonymous/@Edward Campbell does not work on Android because Android is not really Java. Specifically, Android does not have java.lang.ProcessEnvironment
at all. But it turns out to be easier in Android, you just need to do a JNI call to POSIX setenv()
:
事实证明,来自@pushy/@anonymous/@Edward Campbell的解决方案并不适用于Android,因为Android并不是真正的Java。具体来说,Android没有java.lang。ProcessEnvironment。但在Android中,这要容易得多,你只需要给POSIX setenv()打个JNI电话:
In C/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;
}
And in Java:
和在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);
}
}
#8
5
This is a combination of @paul-blair 's answer converted to Java which includes some cleanups pointed out by paul blair and some mistakes that seem to have been inside @pushy 's code which is made up of @Edward Campbell and anonymous.
这是把@paul-blair的答案转换成Java的组合,其中包括paul blair指出的一些清理,以及一些似乎存在于@pushy的代码中的错误,这些代码由@Edward Campbell和anonymous组成。
I cannot emphasize how much this code should ONLY be used in testing and is extremely hacky. But for cases where you need the environment setup in tests it is exactly what I needed.
我无法强调这段代码应该只在测试中使用多少,这是非常陈腐的。但是对于在测试中需要环境设置的情况,这正是我所需要的。
This also includes some minor touches of mine that allow the code to work on both Windows running on
这还包括我的一些小改动,允许代码在两个运行的窗口上运行
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)
as well as Centos running on
还有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)
The implementation:
实现:
/**
* 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));
}
#9
3
Poking around online, it looks like it might be possible to do this with JNI. You'd then have to make a call to putenv() from C, and you'd (presumably) have to do it in a way that worked on both Windows and UNIX.
在网上闲逛,看起来JNI可以做到这一点。然后,您必须从C调用putenv(),并且(可能)必须以在Windows和UNIX上都适用的方式进行调用。
If all that can be done, it surely wouldn't be too hard for Java itself to support this instead of putting me in a straight jacket.
如果这一切都能实现,那么Java本身肯定不会很难支持这一点,而不会让我陷入困境。
A Perl-speaking friend elsewhere suggests that this is because environment variables are process global and Java is striving for good isolation for good design.
其他地方的一位讲perl语言的朋友指出,这是因为环境变量是进程全局的,Java正在努力实现良好的设计。
#10
2
Tried pushy's answer above and it worked for the most part. However, in certain circumstances, I would see this exception:
我尝试了以上的pushy的回答,大部分都是有效的。但是,在某些情况下,我会看到这个例外:
java.lang.String cannot be cast to java.lang.ProcessEnvironment$Variable
This turns out to happen when the method was called more than once, owing to the implementation of certain inner classes of ProcessEnvironment.
If the setEnv(..)
method is called more than once, when the keys are retrieved from the theEnvironment
map, they are now strings (having been put in as strings by the first invocation of setEnv(...)
) and cannot be cast to the map's generic type, Variable,
which is a private inner class of ProcessEnvironment.
由于ProcessEnvironment的某些内部类的实现,当方法被多次调用时,就会出现这种情况。如果setEnv(. .)方法叫做不止一次,当钥匙从环境中获取地图,他们现在字符串(已经把作为字符串的第一个调用setEnv(…)),不能把地图的泛型类型,变量,这是一个私人ProcessEnvironment的内部类。
A fixed version (in Scala), is below. Hopefully it isn't too difficult to carry over into Java.
下面是一个固定的版本(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()
}
}
#11
1
Like most people who have found this thread, I was writing some unit tests and needed to modify the environment variables to set the correct conditions for the test to run. However, I found the most upvoted answers had some issues and/or were very cryptic or overly complicated. Hopefully this will help others to sort out the solution more quickly.
与发现这个线程的大多数人一样,我正在编写一些单元测试,需要修改环境变量,以设置测试运行的正确条件。然而,我发现最令人信服的答案有一些问题和/或非常神秘或过于复杂。希望这能帮助其他人更快地解决问题。
First off, I finally found @Hubert Grzeskowiak's solution to be the simplest and it worked for me. I wish I would have come to that one first. It's based on @Edward Campbell's answer, but without the complicating for loop search.
首先,我最终发现@Hubert Grzeskowiak的解决方案是最简单的,而且它对我很有效。我希望我能先到那个地方。它是基于@Edward Campbell的回答,但是没有复杂的循环搜索。
However, I started with @pushy's solution, which got the most upvotes. It is a combo of @anonymous and @Edward Campbell's. @pushy claims both approaches are needed to cover both Linux and Windows environments. I'm running under OS X and find that both work (once an issue with @anonymous approach is fixed). As others have noted, this solution works most of the time, but not all.
然而,我从@pushy的解决方案开始,得到了最多的支持。它是@anonymous和@Edward Campbell的组合。@pushy声称需要两种方法来覆盖Linux和Windows环境。我在OS X下运行,发现这两种方法都有效(一旦@anonymous方法的问题得到修复)。正如其他人所指出的,这种解决方案大多数时候都是有效的,但不是全部。
I think the source of most of the confusion comes from @anonymous's solution operating on the 'theEnvironment' field. Looking at the definition of the ProcessEnvironment structure, 'theEnvironment' is not a Map< String, String > but rather it is a Map< Variable, Value >. Clearing the map works fine, but the putAll operation rebuilds the map a Map< String, String >, which potentially causes problems when subsequent operations operate on the data structure using the normal API that expects Map< Variable, Value >. Also, accessing/removing individual elements is a problem. The solution is to access 'theEnvironment' indirectly through 'theUnmodifiableEnvironment'. But since this is a type UnmodifiableMap the access must be done through the private variable 'm' of the UnmodifiableMap type. See getModifiableEnvironmentMap2 in code below.
我认为最让人困惑的是@anonymous在“theEnvironment”字段上的解决方案。查看ProcessEnvironment结构的定义,“theEnvironment”不是一个Map< String, String >,而是一个Map< Variable, Value >。清除映射效果良好,但是putAll操作将映射重新构建一个map < String, String >,当后续操作使用期望map < Variable, Value >对数据结构进行操作时,这可能会导致问题。此外,访问/删除单个元素也是一个问题。解决方案是通过“theUnmodifiableEnvironment”间接访问“theEnvironment”。但是由于这是一个类型UnmodifiableMap,所以访问必须通过UnmodifiableMap类型的私有变量m来完成。参见下面代码中的getModifiableEnvironmentMap2。
In my case I needed to remove some of the environment variables for my test (the others should be unchanged). Then I wanted to restore the environment variables to their prior state after the test. The routines below make that straight forward to do. I tested both versions of getModifiableEnvironmentMap on OS X, and both work equivalently. Though based on comments in this thread, one may be a better choice than the other depending on the environment.
在我的例子中,我需要为我的测试删除一些环境变量(其他的应该是不变的)。然后我想在测试之后将环境变量恢复到它们的先前状态。下面的例程会让你直接去做。我在OS X上测试了getModifiableEnvironmentMap的两个版本,它们的工作原理都是一样的。尽管基于这个线程中的注释,根据环境的不同,其中一个可能是更好的选择。
Note: I did not include access to the 'theCaseInsensitiveEnvironmentField' since that seems to be Windows specific and I had no way to test it, but adding it should be straight forward.
注意:我没有包含对“thecaseinsensiveenvironmentfield”的访问,因为这似乎是特定于Windows的,我没有办法对它进行测试,但我补充说,它应该是直接的。
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);
}
#12
-5
You can pass parameters into your initial java process with -D:
您可以使用-D将参数传递到初始的java进程中:
java -cp <classpath> -Dkey1=value -Dkey2=value ...
#1
75
(Is it because this is Java and therefore I shouldn't be doing evil nonportable obsolete things like touching my environment?)
(是因为这是Java,所以我不应该做一些邪恶的、不可移植的、过时的事情,比如触摸我的环境?)
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.
此外,您可能已经知道这一点,但是您可以使用同一个ProcessBuilder启动多个进程。如果你的子进程是一样的,你不需要一遍又一遍地做这个设置。
#2
174
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.
为了在需要为单元测试设置特定环境值的场景中使用,您可能会发现下面的方法是有用的。它将在整个JVM中更改环境变量(因此确保在测试后重置任何更改),但不会改变系统环境。
I found that a combination of the two dirty hacks by Edward Campbell and anonymous works best, as one of the does not work under linux, one does not work under windows 7. So to get a multiplatform evil hack I combined them:
我发现爱德华·坎贝尔(Edward Campbell)和“匿名者”(anonymous)这两个肮脏的黑客组合最好用,因为其中一个在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);
}
}
}
}
This Works like a charm. Full credits to the two authors of these hacks.
这很有魅力。完全归功于两位作者的这些黑客。
#3
42
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);
}
}
}
#4
16
on Android the interface is exposed via Libcore.os as a kind of hidden API.
在Android上,界面是通过Libcore公开的。os是一种隐藏的API。
Libcore.os.setenv("VAR", "value", bOverwrite);
Libcore.os.getenv("VAR"));
The Libcore class as well as the interface OS is public. Just the class declaration is missing and need to be shown to the linker. No need to add the classes to the application, but it also does not hurt if it is included.
Libcore类和接口OS都是公共的。只是缺少类声明,需要显示给链接器。不需要将类添加到应用程序中,但如果包含类,也不会造成伤害。
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;
}
#5
13
// 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);
}
#6
7
Linux only
Setting single environment variables (based on answer by Edward Campbell):
设置单个环境变量(基于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.
首先,把这个方法放到你想要的任何类中,例如SystemUtil。
SystemUtil.setEnv("SHELL", "/bin/bash");
If you call System.getenv("SHELL")
after this, you'll get "/bin/bash"
back.
如果您调用System.getenv(“SHELL”)之后,您将返回“/bin/bash”。
#7
6
It turns out that the solution from @pushy/@anonymous/@Edward Campbell does not work on Android because Android is not really Java. Specifically, Android does not have java.lang.ProcessEnvironment
at all. But it turns out to be easier in Android, you just need to do a JNI call to POSIX setenv()
:
事实证明,来自@pushy/@anonymous/@Edward Campbell的解决方案并不适用于Android,因为Android并不是真正的Java。具体来说,Android没有java.lang。ProcessEnvironment。但在Android中,这要容易得多,你只需要给POSIX setenv()打个JNI电话:
In C/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;
}
And in Java:
和在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);
}
}
#8
5
This is a combination of @paul-blair 's answer converted to Java which includes some cleanups pointed out by paul blair and some mistakes that seem to have been inside @pushy 's code which is made up of @Edward Campbell and anonymous.
这是把@paul-blair的答案转换成Java的组合,其中包括paul blair指出的一些清理,以及一些似乎存在于@pushy的代码中的错误,这些代码由@Edward Campbell和anonymous组成。
I cannot emphasize how much this code should ONLY be used in testing and is extremely hacky. But for cases where you need the environment setup in tests it is exactly what I needed.
我无法强调这段代码应该只在测试中使用多少,这是非常陈腐的。但是对于在测试中需要环境设置的情况,这正是我所需要的。
This also includes some minor touches of mine that allow the code to work on both Windows running on
这还包括我的一些小改动,允许代码在两个运行的窗口上运行
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)
as well as Centos running on
还有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)
The implementation:
实现:
/**
* 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));
}
#9
3
Poking around online, it looks like it might be possible to do this with JNI. You'd then have to make a call to putenv() from C, and you'd (presumably) have to do it in a way that worked on both Windows and UNIX.
在网上闲逛,看起来JNI可以做到这一点。然后,您必须从C调用putenv(),并且(可能)必须以在Windows和UNIX上都适用的方式进行调用。
If all that can be done, it surely wouldn't be too hard for Java itself to support this instead of putting me in a straight jacket.
如果这一切都能实现,那么Java本身肯定不会很难支持这一点,而不会让我陷入困境。
A Perl-speaking friend elsewhere suggests that this is because environment variables are process global and Java is striving for good isolation for good design.
其他地方的一位讲perl语言的朋友指出,这是因为环境变量是进程全局的,Java正在努力实现良好的设计。
#10
2
Tried pushy's answer above and it worked for the most part. However, in certain circumstances, I would see this exception:
我尝试了以上的pushy的回答,大部分都是有效的。但是,在某些情况下,我会看到这个例外:
java.lang.String cannot be cast to java.lang.ProcessEnvironment$Variable
This turns out to happen when the method was called more than once, owing to the implementation of certain inner classes of ProcessEnvironment.
If the setEnv(..)
method is called more than once, when the keys are retrieved from the theEnvironment
map, they are now strings (having been put in as strings by the first invocation of setEnv(...)
) and cannot be cast to the map's generic type, Variable,
which is a private inner class of ProcessEnvironment.
由于ProcessEnvironment的某些内部类的实现,当方法被多次调用时,就会出现这种情况。如果setEnv(. .)方法叫做不止一次,当钥匙从环境中获取地图,他们现在字符串(已经把作为字符串的第一个调用setEnv(…)),不能把地图的泛型类型,变量,这是一个私人ProcessEnvironment的内部类。
A fixed version (in Scala), is below. Hopefully it isn't too difficult to carry over into Java.
下面是一个固定的版本(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()
}
}
#11
1
Like most people who have found this thread, I was writing some unit tests and needed to modify the environment variables to set the correct conditions for the test to run. However, I found the most upvoted answers had some issues and/or were very cryptic or overly complicated. Hopefully this will help others to sort out the solution more quickly.
与发现这个线程的大多数人一样,我正在编写一些单元测试,需要修改环境变量,以设置测试运行的正确条件。然而,我发现最令人信服的答案有一些问题和/或非常神秘或过于复杂。希望这能帮助其他人更快地解决问题。
First off, I finally found @Hubert Grzeskowiak's solution to be the simplest and it worked for me. I wish I would have come to that one first. It's based on @Edward Campbell's answer, but without the complicating for loop search.
首先,我最终发现@Hubert Grzeskowiak的解决方案是最简单的,而且它对我很有效。我希望我能先到那个地方。它是基于@Edward Campbell的回答,但是没有复杂的循环搜索。
However, I started with @pushy's solution, which got the most upvotes. It is a combo of @anonymous and @Edward Campbell's. @pushy claims both approaches are needed to cover both Linux and Windows environments. I'm running under OS X and find that both work (once an issue with @anonymous approach is fixed). As others have noted, this solution works most of the time, but not all.
然而,我从@pushy的解决方案开始,得到了最多的支持。它是@anonymous和@Edward Campbell的组合。@pushy声称需要两种方法来覆盖Linux和Windows环境。我在OS X下运行,发现这两种方法都有效(一旦@anonymous方法的问题得到修复)。正如其他人所指出的,这种解决方案大多数时候都是有效的,但不是全部。
I think the source of most of the confusion comes from @anonymous's solution operating on the 'theEnvironment' field. Looking at the definition of the ProcessEnvironment structure, 'theEnvironment' is not a Map< String, String > but rather it is a Map< Variable, Value >. Clearing the map works fine, but the putAll operation rebuilds the map a Map< String, String >, which potentially causes problems when subsequent operations operate on the data structure using the normal API that expects Map< Variable, Value >. Also, accessing/removing individual elements is a problem. The solution is to access 'theEnvironment' indirectly through 'theUnmodifiableEnvironment'. But since this is a type UnmodifiableMap the access must be done through the private variable 'm' of the UnmodifiableMap type. See getModifiableEnvironmentMap2 in code below.
我认为最让人困惑的是@anonymous在“theEnvironment”字段上的解决方案。查看ProcessEnvironment结构的定义,“theEnvironment”不是一个Map< String, String >,而是一个Map< Variable, Value >。清除映射效果良好,但是putAll操作将映射重新构建一个map < String, String >,当后续操作使用期望map < Variable, Value >对数据结构进行操作时,这可能会导致问题。此外,访问/删除单个元素也是一个问题。解决方案是通过“theUnmodifiableEnvironment”间接访问“theEnvironment”。但是由于这是一个类型UnmodifiableMap,所以访问必须通过UnmodifiableMap类型的私有变量m来完成。参见下面代码中的getModifiableEnvironmentMap2。
In my case I needed to remove some of the environment variables for my test (the others should be unchanged). Then I wanted to restore the environment variables to their prior state after the test. The routines below make that straight forward to do. I tested both versions of getModifiableEnvironmentMap on OS X, and both work equivalently. Though based on comments in this thread, one may be a better choice than the other depending on the environment.
在我的例子中,我需要为我的测试删除一些环境变量(其他的应该是不变的)。然后我想在测试之后将环境变量恢复到它们的先前状态。下面的例程会让你直接去做。我在OS X上测试了getModifiableEnvironmentMap的两个版本,它们的工作原理都是一样的。尽管基于这个线程中的注释,根据环境的不同,其中一个可能是更好的选择。
Note: I did not include access to the 'theCaseInsensitiveEnvironmentField' since that seems to be Windows specific and I had no way to test it, but adding it should be straight forward.
注意:我没有包含对“thecaseinsensiveenvironmentfield”的访问,因为这似乎是特定于Windows的,我没有办法对它进行测试,但我补充说,它应该是直接的。
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);
}
#12
-5
You can pass parameters into your initial java process with -D:
您可以使用-D将参数传递到初始的java进程中:
java -cp <classpath> -Dkey1=value -Dkey2=value ...