[java] Java에서 환경 변수를 설정하는 방법

Java에서 환경 변수를 설정하는 방법 를 사용하여 하위 프로세스에 대해이 작업을 수행 할 수 있음을 알았습니다 ProcessBuilder. 그래도 시작할 하위 프로세스가 여러 개 있으므로 현재 프로세스 환경을 수정하고 하위 프로세스가 상속하도록합니다.

있어 System.getenv(String)단일 환경 변수를 얻기 위해. 로 Map전체 환경 변수 세트를 얻을 수도 있습니다 System.getenv(). 그러나, 호출 put()그것에 것은 Map발생 UnsupportedOperationException– 환경이 읽기 전용을 위해 분명히 그 의미. 그리고, 없습니다 System.setenv().

현재 실행중인 프로세스에서 환경 변수를 설정하는 방법이 있습니까? 그렇다면 어떻게? 그렇지 않다면 근거는 무엇입니까? (이것은 Java이기 때문에 환경을 만지는 것과 같이 악의적이며 쓸모없는 쓸데없는 일을해서는 안됩니까?) 그렇지 않다면 환경 변수를 관리하기위한 좋은 제안은 하위 프로세스?



답변

(이것은 Java이기 때문에 환경에 닿는 것과 같은 악의적이며 쓸모없는 쓸데없는 일을해서는 안됩니까?)

나는 당신이 머리에 못을 박았다고 생각합니다.

부담을 완화하는 가능한 방법은 방법을 제외시키는 것입니다

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

ProcessBuilder시작하기 전에 s를 통과하십시오 .

또한 이미 알고있을 수도 있지만 같은로 여러 프로세스를 시작할 수 있습니다 ProcessBuilder. 따라서 하위 프로세스가 동일하면이 설정을 반복해서 수행 할 필요가 없습니다.


답변

단위 테스트를 위해 특정 환경 값을 설정해야하는 시나리오에서 사용하려면 다음 해킹이 유용 할 수 있습니다. JVM 전체에서 환경 변수가 변경되므로 테스트 후 변경 사항을 재설정해야하지만 시스템 환경은 변경되지 않습니다.

나는 에드워드 캠벨 (Edward Campbell)의 두 가지 더러운 해킹과 익명의 조합이 가장 잘 작동한다는 것을 알았습니다. 하나는 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);
        }
    }
}

또는 joshwolfe의 제안에 따라 단일 var를 추가 / 업데이트하고 루프를 제거하십시오.

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


답변

안드로이드에서 인터페이스는 일종의 숨겨진 API로 Libcore.os를 통해 노출됩니다.

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

인터페이스 OS뿐만 아니라 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;
}


답변

리눅스 만

단일 환경 변수 설정 (Edward Campbell의 답변 기반) :

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"다시 돌아옵니다.


답변

이것은 자바로 변환 된 @ paul-blair의 답변과 paul blair에 의해 지적 된 정리 및 @Edward Campbell으로 구성된 @pushy 코드 내부에있는 것으로 보이는 일부 실수와 익명을 포함하는 조합입니다.

나는이 코드가 테스트에 얼마나 사용되어야하는지 강조 할 수 없으며 매우 해킹 적이다. 그러나 테스트에서 환경 설정이 필요한 경우 정확히 필요합니다.

여기에는 코드가 작동하는 두 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));
}