ver variable restaurar modificar java_home entorno configurar java environment-variables

restaurar - ¿Cómo configuro las variables de entorno desde Java?



variables de entorno java linux (13)

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

Creo que has golpeado el clavo en la cabeza.

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

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

y pase cualquier ProcessBuilder s a través de él antes de iniciarlos.

Además, probablemente ya lo sepa, pero puede iniciar más de un proceso con el mismo ProcessBuilder . Entonces, si sus subprocesos son iguales, no necesita hacer esta configuración una y otra vez.

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

Hay un System.getenv (String) para obtener una única variable de entorno. También puedo obtener un Mapa del conjunto completo de variables de entorno con System.getenv (). Pero llamar a put () en ese Mapa genera una excepción de operación no admitida - aparentemente significa que el entorno es de solo lectura. Y no hay System.setenv ().

Entonces, ¿hay alguna manera de establecer variables de entorno en el proceso actualmente en ejecución? ¿Si es así, cómo? Si no es así, ¿cuál es la razón? (¿Es porque esto es Java y, por lo tanto, no debería estar haciendo cosas malvadas, obsoletas y no portátiles, como tocar mi entorno)? subprocesos?


Solo linux

Configuración de variables de entorno individuales (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, ponga el método en la clase que desee, por ejemplo, SystemUtil.

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

Si llama a System.getenv("SHELL") después de esto, obtendrá "/bin/bash" nuevamente.


Como la mayoría de las personas que han encontrado este hilo, estaba escribiendo algunas pruebas unitarias y necesitaba modificar las variables de entorno para establecer las condiciones correctas para que se ejecutara la prueba. Sin embargo, encontré que las respuestas más elevadas tenían algunos problemas y / o eran muy crípticas o demasiado complicadas. Esperemos que esto ayude a otros a resolver la solución más rápidamente.

En primer lugar, finalmente encontré que la solución de @Hubert Grzeskowiak era la más sencilla y funcionó para mí. Ojalá hubiera venido a eso primero. Se basa en la respuesta de @Edward Campbell, pero sin la complicación de la búsqueda en bucle.

Sin embargo, empecé con la solución de @ pushy, que obtuvo el mayor número de votos. Es un combo de @anonymous y @Edward Campbell''s. @pushy afirma que ambos enfoques son necesarios para cubrir los entornos de Linux y Windows. Estoy ejecutando bajo OS X y encuentro que ambos funcionan (una vez que se solucionó un problema con el enfoque anónimo). Como han dicho otros, esta solución funciona la mayor parte del tiempo, pero no todas.

Creo que la fuente de la mayor parte de la confusión proviene de la solución de @ anonymous que opera en el campo ''theEnvironment''. Al ProcessEnvironment la definición de la estructura de ProcessEnvironment , ''theEnvironment'' no es un Mapa <String, String> sino un Mapa <Variable, Valor>. Limpiar el mapa funciona bien, pero la operación putAll reconstruye el mapa como un <Cadena, Cadena>, lo que potencialmente causa problemas cuando las operaciones subsiguientes operan en la estructura de datos usando la API normal que espera el Mapa <Variable, Valor>. Además, el acceso / eliminación de elementos individuales es un problema. La solución es acceder a ''theEnvironment'' indirectamente a través de ''theModifiableEnvironment''. Pero dado que este es un tipo UnmodifiableMap el acceso debe realizarse a través de la variable privada ''m'' del tipo UnmodifiableMap. Consulte getModifiableEnvironmentMap2 en el código a continuación.

En mi caso, necesitaba eliminar algunas de las variables de entorno para mi prueba (las otras no deberían cambiar). Luego quise restaurar las variables de entorno a su estado anterior después de la prueba. Las rutinas a continuación hacen que esto sea sencillo. Probé ambas versiones de getModifiableEnvironmentMap en OS X, y ambas funcionan de manera equivalente. Aunque se basa en los comentarios de este hilo, uno puede ser una mejor opción que el otro dependiendo del entorno.

Nota: No incluí el acceso al ''theCaseInsensitiveEnvironmentField'' ya que parece ser específico de Windows y no tenía forma de probarlo, pero agregarlo debería ser sencillo.

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


Esta es una combinación de la respuesta de @paul-blair convertida a Java que incluye algunas limpiezas señaladas por paul blair y algunos errores que parecen haber estado dentro del código de @pushy, que está formado por @Edward Campbell y anónimo.

No puedo enfatizar cuánto este código SOLO debe usarse en las pruebas y es extremadamente intrépido. Pero para los casos en que necesite la configuración del entorno en las pruebas, es exactamente lo que necesitaba.

Esto también incluye algunos de mis pequeños detalles que permiten que el código funcione tanto en Windows que se ejecuta en

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)

así como Centos corriendo en

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)

La implementación:

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


Hurgando en línea, parece que podría ser posible hacer esto con JNI. Luego tendría que hacer una llamada a putenv () desde C, y (presumiblemente) tendría que hacerlo de una manera que funcionara tanto en Windows como en UNIX.

Si se puede hacer todo eso, seguramente no sería demasiado difícil para el propio Java admitir esto en lugar de ponerme una chaqueta recta.

Un amigo que habla perl en otro lugar sugiere que esto se debe a que las variables de entorno son procesos globales y Java se esfuerza por lograr un buen aislamiento para un buen diseño.


La implementación de Kotlin que hice recientemente basada en la respuesta de Edward:

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


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

Descubrí que una combinación de los dos hacks sucios de Edward Campbell y anónimo funciona mejor, ya que uno de los que no funciona en Linux, uno no funciona en Windows 7. Así que 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 como un encanto. Créditos completos a los dos autores de estos hacks.


Probé la respuesta de Pushy arriba y funcionó en su mayor parte. Sin embargo, en ciertas circunstancias, vería esta excepción:

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

Esto sucede cuando el método fue llamado más de una vez, debido a la implementación de ciertas clases internas de ProcessEnvironment. Si el setEnv(..) se llama más de una vez, cuando las claves se recuperan del mapa del theEnvironment , ahora son cadenas (se han colocado como cadenas por la primera invocación de setEnv(...) ) y no pueden ser ProcessEnvironment. tipo genérico del mapa, Variable, que es una clase interna privada de ProcessEnvironment.

Una versión fija (en Scala), está abajo. Esperemos que no sea demasiado difícil de llevar a 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() } }


Puede pasar parámetros a su proceso java inicial con -D:

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


Resulta que la solución de @ pushy / @ anonymous / @ Edward Campbell no funciona en Android porque en realidad Android no es Java. Específicamente, Android no tiene java.lang.ProcessEnvironment en absoluto. Pero resulta ser más fácil en Android, solo necesitas hacer una llamada JNI a POSIX setenv() :

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

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


en Android, la interfaz se expone a través de Libcore.os como una especie de API oculta.

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

La clase Libcore y el sistema operativo de interfaz son públicos. Solo falta la declaración de clase y debe mostrarse al vinculador. No es necesario agregar las clases a la aplicación, pero tampoco se pierde si se incluye.

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


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