¿Cómo configuro variables de entorno desde Java?

Resuelto skiphoppy asked hace 16 años • 24 respuestas

¿Cómo configuro variables de entorno desde Java? Veo que puedo hacer esto para subprocesos usando ProcessBuilder. Sin embargo, tengo varios subprocesos para iniciar, por lo que prefiero modificar el entorno del proceso actual y dejar que los subprocesos lo hereden.

Existe una opción System.getenv(String)para obtener una única variable de entorno. También puedo obtener una parte Mapdel conjunto completo de variables de entorno con System.getenv(). Pero, put()al invocar eso Mapse genera un UnsupportedOperationException... aparentemente quieren decir que el entorno sea de solo lectura. Y no hay System.setenv().

Entonces, ¿hay alguna forma de establecer variables de entorno en el proceso que se está ejecutando actualmente? ¿Si es así, cómo? Si no, ¿cuál es el motivo? (¿Es porque esto es Java y, por lo tanto, no debería estar haciendo cosas obsoletas no portátiles como tocar mi entorno?) Y si no, ¿alguna buena sugerencia para gestionar los cambios en las variables de entorno que tendré que alimentar a varios subprocesos?

skiphoppy avatar Nov 26 '08 00:11 skiphoppy
Aceptado

Para usar en escenarios donde necesita establecer valores de entorno específicos para pruebas unitarias, el siguiente truco puede resultarle útil. Cambiará las variables de entorno en toda la JVM (así que asegúrese de restablecer cualquier cambio después de la prueba), pero no alterará el entorno de su sistema.

Descubrí que una combinación de los dos trucos sucios de Edward Campbell y anónimo funciona mejor, ya que uno no funciona en Linux y el otro no funciona en Windows 7. Entonces, para obtener un truco malvado multiplataforma, los combiné:

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

Esto funciona de maravilla. Créditos completos a los dos autores de estos trucos.

pushy avatar Aug 26 '2011 08:08 pushy

(¿Es porque esto es Java y, por lo tanto, no debería estar haciendo cosas malvadas y obsoletas, no portátiles, como tocar mi entorno?)

Creo que has dado en el clavo.

Una posible forma de aliviar la carga sería descartar un método

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

y pase cualquier ProcessBuildermensaje a través de él antes de iniciarlo.

Además, probablemente ya lo sepas, pero puedes iniciar más de un proceso con el mismo ProcessBuilder. Entonces, si sus subprocesos son los mismos, no necesita realizar esta configuración una y otra vez.

Michael Myers avatar Nov 25 '2008 17:11 Michael Myers
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);
        }
    }
}

O agregar/actualizar una sola var y eliminar el bucle según la sugerencia de thejoshwolfe.

@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);
  }
 avatar Jan 30 '2009 19:01

Establecer variables de entorno únicas (basadas en la respuesta de 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);
    }
}

Uso:

Primero, coloque el método en cualquier clase que desee, por ejemplo, SystemUtil. Luego llámalo estáticamente:

SystemUtil.setEnv("SHELL", "/bin/bash");

Si llamas System.getenv("SHELL")después de esto, volverás "/bin/bash".

Hubert Grzeskowiak avatar Nov 18 '2016 16:11 Hubert Grzeskowiak