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

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

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

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

13 ответов


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

Я думаю, вы попали в точку.

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

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

и ProcessBuilders Через него, прежде чем начать их.

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


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

Я обнаружил, что комбинация двух грязных хаков Эдварда Кэмпбелла и anonymous работает лучше всего, так как один из них не работает под linux, один не работает под windows 7. Так что, чтобы получить multiplatform зло hack я объединил их:

protected static void setEnv(Map<String, String> newenv) throws Exception {
  try {
    Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
    Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
    theEnvironmentField.setAccessible(true);
    Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
    env.putAll(newenv);
    Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
    theCaseInsensitiveEnvironmentField.setAccessible(true);
    Map<String, String> cienv = (Map<String, String>)     theCaseInsensitiveEnvironmentField.get(null);
    cienv.putAll(newenv);
  } catch (NoSuchFieldException e) {
    Class[] classes = Collections.class.getDeclaredClasses();
    Map<String, String> env = System.getenv();
    for(Class cl : classes) {
      if("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
        Field field = cl.getDeclaredField("m");
        field.setAccessible(true);
        Object obj = field.get(env);
        Map<String, String> map = (Map<String, String>) obj;
        map.clear();
        map.putAll(newenv);
      }
    }
  }
}

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


public static void set(Map<String, String> newenv) throws Exception {
    Class[] classes = Collections.class.getDeclaredClasses();
    Map<String, String> env = System.getenv();
    for(Class cl : classes) {
        if("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
            Field field = cl.getDeclaredField("m");
            field.setAccessible(true);
            Object obj = field.get(env);
            Map<String, String> map = (Map<String, String>) obj;
            map.clear();
            map.putAll(newenv);
        }
    }
}

на Android интерфейс предоставляется через Libcore.ОС как своего рода скрытый API.

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

класс Libcore, а также ОС интерфейса являются общедоступными. Просто объявление класса отсутствует и должно быть показано компоновщику. Нет необходимости добавлять классы в приложение, но это также не повредит, если он включен.

package libcore.io;

public final class Libcore {
    private Libcore() { }

    public static Os os;
}

package libcore.io;

public interface Os {
    public String getenv(String name);
    public void setenv(String name, String value, boolean overwrite) throws ErrnoException;
}

// this is a dirty hack - but should be ok for a unittest.
private void setNewEnvironmentHack(Map<String, String> newenv) throws Exception
{
  Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
  Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
  theEnvironmentField.setAccessible(true);
  Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
  env.clear();
  env.putAll(newenv);
  Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
  theCaseInsensitiveEnvironmentField.setAccessible(true);
  Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
  cienv.clear();
  cienv.putAll(newenv);
}

только 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" обратно.


оказывается, что решение от @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);
    }
}

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

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

Это также включает в себя некоторые незначительные штрихи мои, которые позволяют коду работать на обоих окнах, работающих на

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

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

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

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


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

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

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

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

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

Я думаю, что источником большей части путаницы является решение @anonymous, работающее в поле "theEnvironment". Глядя на определение ProcessEnvironment structure, 'theEnvironment' не является Map , а скорее это Map. Очистка карты работает нормально, но операция putAll перестраивает карту Map, что потенциально вызывает проблемы при последующих операциях структура данных с использованием обычного API, который ожидает Map. Кроме того, доступ/удаление отдельных элементов является проблемой. Решение получить доступ к 'окружающей среды' косвенно 'theUnmodifiableEnvironment'. Но так как это тип 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);
}

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

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

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

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