Как установить переменные среды из Java?

Как установить переменные окружения из Java? Я вижу, что я могу сделать это для подпроцессов, используя ProcessBuilder. У меня есть несколько подпроцессов для запуска, поэтому я бы предпочел изменить текущую среду процессов и позволить подпроцессам наследовать ее.

Есть System.getenv(String) для получения одной переменной среды. Я также могу получить Map полного набора переменных среды с помощью System.getenv(). Но вызов метода put() для этого Map вызывает UnsupportedOperationException - очевидно, они означают, что среда должна быть доступна только для чтения. И нет System.setenv().

Итак, есть ли способ установить переменные среды в текущем процессе? Если да, то как? Если нет, в чем причина? (Это потому, что это Java, и поэтому я не должен делать злых непереносимых устаревших вещей, таких как прикосновение к моей среде?) И если нет, то любые полезные предложения по управлению изменениями переменных среды, которые мне нужно будет передать нескольким подпроцессы?

Ответ 1

(Является ли это потому, что это Java, и поэтому я не должен делать злые непереносимые устаревшие вещи, такие как касание моей среды?)

Я думаю, что ты ударил ноготь по голове.

Возможным способом облегчения бремени было бы исключить метод

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

и пропустите через него ProcessBuilder, прежде чем запускать их.

Кроме того, вы, вероятно, уже знаете это, но вы можете запустить более одного процесса с тем же ProcessBuilder. Поэтому, если ваши подпроцессы одинаковы, вам не нужно делать эту настройку снова и снова.

Ответ 2

Для использования в сценариях, где вам нужно установить определенные значения среды для модульных тестов, вы можете найти следующий взлом полезный. Он изменит переменные среды во всей JVM (поэтому убедитесь, что вы reset какие-либо изменения после теста), но не изменит вашу системную среду.

Я обнаружил, что комбинация двух грязных хаков Эдвардом Кэмпбеллом и анонимным работает лучше всего, поскольку один из них не работает под Linux, один из них не работает под окнами 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);
      }
    }
  }
}

Это работает как шарм. Полные кредиты двум авторам этих хаков.

Ответ 3

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

Или добавить/обновить одну переменную и удалить цикл в соответствии с предложением joshwolfe.

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

Ответ 4

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

Ответ 5

на 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;
}

Ответ 6

Только для Linux

Настройка отдельных переменных среды (на основе ответа Эдварда Кэмпбелла):

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" назад.

Ответ 7

Оказывается, решение от @pushy/@anonymous/@Edward Campbell не работает на Android, потому что Android на самом деле не Java. В частности, Android вообще не имеет java.lang.ProcessEnvironment. Но в Android вам будет проще, вам просто нужно сделать JNI-вызов POSIX setenv():

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

Ответ 8

Это комбинация ответа @paul-blair, преобразованного в Java, который включает некоторые очистки, отмеченные paul blair, и некоторые ошибки, которые, кажется, были внутри @pushy кода, который состоит из @Edward Campbell и анонимного.

Я не могу подчеркнуть, насколько этот код должен ТОЛЬКО использоваться в тестировании и крайне хакерский. Но для случаев, когда вам нужна настройка среды в тестах, это именно то, что мне нужно.

Это также включает некоторые незначительные изменения, которые позволяют коду работать на обеих 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 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

В режиме онлайн он выглядит так, как можно было бы сделать это с JNI. Затем вам нужно сделать вызов putenv() из C, и вы (предположительно) должны сделать это так, как с Windows, так и с UNIX.

Если все, что можно сделать, наверняка было бы не слишком сложно для самой Java, чтобы поддерживать это, вместо того, чтобы вставлять меня в прямую куртку.

Друг, говорящий на Perl, говорит о том, что это связано с тем, что переменные среды являются глобальными процессами, а Java стремится к хорошей изоляции для хорошего дизайна.

Ответ 10

Пробовал напористый ответ выше, и он работал по большей части. Однако в определенных обстоятельствах я бы увидел это исключение:

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

Это происходит, когда метод вызывается более одного раза из-за реализации некоторых внутренних классов ProcessEnvironment. Если метод setEnv(..) вызывается более одного раза, когда ключи извлекаются из theEnvironment map, теперь они являются строками (которые были помещены как строки с помощью первого вызова setEnv(...)) и не могут быть отнесены к родовому типу карты, Variable,, который является частным внутренним классом 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()
  }
}

Ответ 11

Как и большинство людей, которые нашли этот поток, я писал некоторые модульные тесты и нуждался в изменении переменных среды, чтобы установить правильные условия для запуска теста. Тем не менее, я обнаружил, что ответы на наиболее распространенные ответы были связаны с некоторыми проблемами и/или были очень загадочными или чрезмерно сложными. Надеюсь, это поможет другим быстрее решить проблему.

Во-первых, я, наконец, нашел решение @Hubert Grzeskowiak самым простым, и это сработало для меня. Хотелось бы, чтобы я впервые пришел к этому. Это основано на ответе @Edward Campbell, но без усложнения поиска цикла.

Тем не менее, я начал с решения @pushy, которое получило максимальную отдачу. Это комбо @anonymous и @Edward Campbell's. @pushy утверждает, что оба подхода необходимы для покрытия как Linux, так и Windows. Я работаю под OS X и обнаруживаю, что обе работают (как только проблема с @anonymous подход исправлена). Как отмечали другие, это решение работает большую часть времени, но не все.

Я думаю, что источником большинства путаницы является @anonymous решение, работающее в поле theEnvironment. Рассматривая определение структуры ProcessEnvironment, 'theEnvironment' не является Map < String, String > , а скорее это Map < Переменная, Значение > . Очистка карты работает нормально, но операция putAll перестраивает карту Map < String, String > , что потенциально вызывает проблемы, когда последующие операции работают с структурой данных, используя обычный API, который ожидает, что Map < Переменная, Значение > . Кроме того, проблема с доступом/удалением отдельных элементов. Решение состоит в том, чтобы косвенно получить доступ к "Окружающей среде" через "Неумолимую окружающую среду". Но поскольку это тип UnmodifiableMap, доступ должен выполняться через закрытую переменную 'm' типа UnmodifiableMap. См. GetModifiableEnvironmentMap2 в коде ниже.

В моем случае мне нужно было удалить некоторые переменные среды для моего теста (остальные не должны быть изменены). Затем я захотел восстановить переменные среды в их предыдущем состоянии после теста. Подпрограммы ниже делают это прямо вперед. Я тестировал обе версии getModifiableEnvironmentMap на OS X, и оба они работают одинаково. Хотя на основе комментариев в этом потоке, один может быть лучшим выбором, чем другой, в зависимости от среды.

Примечание. Я не включил доступ к "theCaseInsensitiveEnvironmentField", поскольку это похоже на 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

Это злая версия Kotlin @pushy злой ответ =)

@Suppress("UNCHECKED_CAST")
@Throws(Exception::class)
fun setEnv(newenv: Map<String, String>) {
    try {
        val processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment")
        val theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment")
        theEnvironmentField.isAccessible = true
        val env = theEnvironmentField.get(null) as MutableMap<String, String>
        env.putAll(newenv)
        val theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment")
        theCaseInsensitiveEnvironmentField.isAccessible = true
        val cienv = theCaseInsensitiveEnvironmentField.get(null) as MutableMap<String, String>
        cienv.putAll(newenv)
    } catch (e: NoSuchFieldException) {
        val classes = Collections::class.java.getDeclaredClasses()
        val env = System.getenv()
        for (cl in classes) {
            if ("java.util.Collections\$UnmodifiableMap" == cl.getName()) {
                val field = cl.getDeclaredField("m")
                field.setAccessible(true)
                val obj = field.get(env)
                val map = obj as MutableMap<String, String>
                map.clear()
                map.putAll(newenv)
            }
        }
    }

Это работает в MacOS Mojave по крайней мере.

Ответ 13

Если вы работаете с SpringBoot, вы можете добавить указание переменной среды в следующем свойстве:

was.app.config.properties.toSystemProperties

Ответ 14

вариант, основанный на ответе @pushy, работает в Windows.

def set_env(newenv):
    from java.lang import Class
    process_environment = Class.forName("java.lang.ProcessEnvironment")
    environment_field =  process_environment.getDeclaredField("theEnvironment")
    environment_field.setAccessible(True)
    env = environment_field.get(None)
    env.putAll(newenv)
    invariant_environment_field = process_environment.getDeclaredField("theCaseInsensitiveEnvironment");
    invariant_environment_field.setAccessible(True)
    invevn = invariant_environment_field.get(None)
    invevn.putAll(newenv)

Использование:

old_environ = dict(os.environ)
old_environ['EPM_ORACLE_HOME'] = r"E:\Oracle\Middleware\EPMSystem11R1"
set_env(old_environ)

Ответ 15

Реализация Kotlin, которую я недавно сделал на основе ответа Эдварда:

fun setEnv(newEnv: Map<String, String>) {
    val unmodifiableMapClass = Collections.unmodifiableMap<Any, Any>(mapOf()).javaClass
    with(unmodifiableMapClass.getDeclaredField("m")) {
        isAccessible = true
        @Suppress("UNCHECKED_CAST")
        get(System.getenv()) as MutableMap<String, String>
    }.apply {
        clear()
        putAll(newEnv)
    }
}

Ответ 16

Вы можете передать параметры в свой первоначальный Java-процесс с помощью -D:

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