Как установить переменные окружения из Java? - PullRequest
258 голосов
/ 25 ноября 2008

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

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

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

Ответы [ 14 ]

203 голосов
/ 26 августа 2011

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

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

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

84 голосов
/ 25 ноября 2008

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

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

Возможный способ облегчить бремя - это выделить метод

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

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

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

50 голосов
/ 30 января 2009
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);
        }
    }
}

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

@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);
  }
17 голосов
/ 20 декабря 2010
// 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);
}
16 голосов
/ 11 марта 2014

на 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;
}
10 голосов
/ 18 ноября 2016

только для 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 голосов
/ 07 декабря 2013

Оказывается, что решение от @ 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);
    }
}
6 голосов
/ 28 июня 2016

Это сочетание ответа @ paul-blair, преобразованного в Java, которое включает в себя некоторые исправления, указанные Полом Блэром, и некоторые ошибки, которые, кажется, были внутри кода @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'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));
}
3 голосов
/ 23 марта 2017

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

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

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

Я думаю, что источником большей части путаницы является решение @ anonymous, работающее в области 'TheEnvironment'. Если посмотреть на определение структуры ProcessEnvironment , то 'theEnvironment' - это не карта , а карта <переменная, значение>. Очистка карты работает нормально, но операция putAll перестраивает карту Map , что потенциально вызывает проблемы, когда последующие операции работают с структурой данных с использованием обычного 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);
}
3 голосов
/ 27 сентября 2013

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

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()
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...