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



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



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



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

817   13  

13 ответов:

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

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

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

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

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

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

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

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

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

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

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

в моем случае мне нужно было удалить часть среды переменные для моего теста (остальные должны быть неизменными). Затем я хотел восстановить переменные среды до их предыдущего состояния после теста. Процедуры ниже делают это прямо вперед, чтобы сделать. Я тестировал обе версии getmodifiableenvironmap на 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 ...

Comments

    Ничего не найдено.