Вопрос

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

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

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

Это было полезно?

Решение

  

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

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

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

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

и пропустите все ProcessBuilder через него перед запуском.

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

Другие советы

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

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

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

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

Только для 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 и анонимный.

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

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

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

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

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

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

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

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

Фиксированная версия (в 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 самым простым, и оно сработало для меня. Я хотел бы прийти к этому в первую очередь. Он основан на ответе Эдварда Кэмпбелла, но без усложнения поиска в цикле.

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

Я думаю, что источником большей части путаницы является решение @ anonymous, работающее в области theEnvironment. Рассматривая определение Структура ProcessEnvironment , 'theEnvironment' не является картой < String, String & Gt; скорее это карта < Переменная, Значение & Gt ;. Очистка карты работает нормально, но операция putAll перестраивает карту a Map & Lt; String, String & Gt ;, что потенциально вызывает проблемы, когда последующие операции работают с структурой данных с использованием обычного API, который ожидает Map & Lt; Переменная, Значение & Gt ;. Кроме того, доступ / удаление отдельных элементов является проблемой. Решение состоит в том, чтобы получить доступ к «Среде» косвенно через «Немодифицируемую Среду». Но так как это тип 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);
}

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

@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.

Реализация 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)
    }
}

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

java -cp <classpath> -Dkey1=value -Dkey2=value ...
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top