¿Cómo obtengo las variables de entorno de otro proceso?

24

Si examino /proc/1/environpuedo ver una cadena delimitada por bytes nulos de 1las variables de entorno del proceso . Me gustaría traer estas variables a mi entorno actual. ¿Hay una forma fácil de hacer esto?

La procpágina de manual me da un fragmento que ayuda a imprimir cada variable de entorno línea por línea (cat /proc/1/environ; echo) | tr '\000' '\n'. Esto me ayuda a verificar que el contenido sea correcto, pero lo que realmente necesito hacer es obtener estas variables en mi sesión de bash actual.

¿Cómo puedo hacer eso?

Dane O'Connor
fuente

Respuestas:

23

Lo siguiente convertirá cada variable de entorno en un exportenunciado, correctamente citado para leer en un shell (porque LS_COLORS, por ejemplo, es probable que tenga punto y coma), luego lo obtiene.

[El printfen /usr/bin, por desgracia, en general, no admite %q, por lo que tenemos que llamar a la construida en bash.]

. <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ)
Mark Plotnick
fuente
Sugiero . <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ), que también manejará las variables con comillas en ellas correctamente.
John Kugelman apoya a Monica el
@JohnKugelman Muchas gracias por la mejora, usando en "$@"lugar de '{}'. Para aquellos que se preguntan sobre el --argumento en su respuesta mejorada: los argumentos posicionales a los que bash -c command_stringse asignan comienzan en $0, mientras que se "$@"expanden para incluir argumentos que comienzan en $1. El argumento --se asigna a $0.
Mark Plotnick
10

En bashpuedes hacer lo siguiente. Esto funcionará para todos los contenidos posibles de las variables y evita eval:

while IFS= read -rd '' var; do declare +x "$var"; done </proc/$PID/environ

Esto declarará las variables leídas como variables de shell en el shell en ejecución. Para exportar las variables al entorno de shell en ejecución:

while IFS= read -rd '' var; do export "$var"; done </proc/$PID/environ
Graeme
fuente
10

En esta respuesta, supongo que un sistema /proc/$pid/environdevuelve el entorno del proceso con el PID especificado, con bytes nulos entre las definiciones de variables. ( Entonces Linux, Cygwin o Solaris (?) ).

Zsh

export "${(@ps:\000:)$(</proc/$pid/environ)}"

(Bastante simple como zsh: una redirección de entrada sin comando <FILE es equivalente a cat FILE. La salida de la sustitución del comando sufre expansión de parámetros con las banderas que ps:\000: significan "dividido en bytes nulos", y que @significa "si todo está entre comillas dobles, entonces trate cada elemento de la matriz como un campo separado ”(generalizando "$@").)

Bash, mksh

while IFS= read -r -d "" PWD; do export "$PWD"; done </proc/$pid/environ
PWD=$(pwd)

(En estos shells, un delimitador vacío pasado a readresultados en bytes nulos como separadores. Lo uso PWDcomo un nombre de variable temporal para evitar tropezar con otra variable que podría terminar siendo importada. Si bien técnicamente también podría importar PWD, solo permanecería allí hasta el siguiente cd)

POSIX

La portabilidad POSIX no es tan interesante para esta pregunta, porque solo se aplica a los sistemas que tienen /proc/PID/environ. Entonces, la pregunta es qué admite Solaris sed, o si Solaris lo ha hecho /proc/PID/environ, no solía hacerlo, pero estoy muy por detrás de la curva de las características de Solaris, por lo que podría ser hoy en día. En Linux, las utilidades GNU y BusyBox son a prueba de nulos, pero con advertencias.

Si insistimos en la portabilidad POSIX, no se requiere ninguna de las utilidades de texto POSIX para manejar bytes nulos, por lo que esto es difícil. Aquí hay una solución que supone que awk admite un byte nulo como delimitador de registro (nawk y gawk lo hacen, al igual que BusyBox awk, pero mawk no).

eval $(</proc/$pid/environ awk -v RS='\0' '{gsub("\047", "\047\\\047\047"); print "export \047" $0 "\047"}')

BusyBox awk (que es la versión que se encuentra comúnmente en los sistemas embebidos Linux) hace nulo apoyo bytes, pero no establecer RSque "\0"en un BEGINbloque y no la sintaxis de línea de comandos anterior; Sin embargo, es compatible -v 'RS="\0"'. No he investigado por qué, esto parece un error en mi versión (Debian wheezy).

(Ajuste todas las líneas de registros separados por nulos en comillas simples "\047", después de escapar de las comillas simples dentro de los valores).

Advertencias

Tenga en cuenta que cualquiera de estos puede intentar establecer variables de solo lectura (si su shell tiene variables de solo lectura).

Gilles 'SO- deja de ser malvado'
fuente
Finalmente volví a esto. Descubrí un medio infalible de hacer esto o cualquier manejo nulo en todos los shells que conozco de manera bastante simple. Mira mi nueva respuesta.
mikeserv
6

Di vueltas y vueltas con esto. Estaba frustrado con la portabilidad de bytes nulos. No me sentó bien que no hubiera una forma confiable de manejarlos en un caparazón. Así que seguí buscando. La verdad es que encontré varias formas de hacer esto, solo algunas de las cuales se mencionan en mi otra respuesta. Pero los resultados fueron al menos dos funciones de shell que funcionan así:

_pidenv ${psrc=$$} ; _zedlmt <$near_any_type_of_file

Primero hablaré sobre la \0delimitación. En realidad es bastante fácil de hacer. Aquí está la función:

_zedlmt() { od -t x1 -w1 -v  | sed -n '
    /.* \(..\)$/s//\1/
    /00/!{H;b};s///
    x;s/\n/\\x/gp;x;h'
}

Básicamente odtoma stdiny escribe en stdoutcada byte que recibe en hexadecimal uno por línea.

printf 'This\0is\0a\0lot\0\of\0\nulls.' |
    od -t x1 -w1 -v
    #output
0000000 54
0000001 68
0000002 69
0000003 73
0000004 00
0000005 69
0000006 73
    #and so on

Apuesto a que puedes adivinar cuál es el \0null, ¿verdad? Escrito así es fácil de manejar con cualquiera sed . sedsolo guarda los dos últimos caracteres en cada línea hasta que encuentra un valor nulo en el que reemplaza las nuevas líneas intermedias con printfun código de formato amigable e imprime la cadena. El resultado es una \0nullmatriz delimitada de cadenas de bytes hexadecimales. Mira:

printf %b\\n $(printf 'Fewer\0nulls\0here\0.' |
    _zedlmt | tee /dev/stderr)
    #output
\x46\x65\x77\x65\x72
\x6e\x75\x6c\x6c\x73
\x68\x65\x72\x65
\x2e
Fewer
nulls
here
.

Lo canalicé arriba para teeque pueda ver tanto la salida de la suspensión del comando como el resultado del printfprocesamiento. Espero que se dé cuenta de que la subshell en realidad tampoco se cita pero se printfdivide solo en el \0nulldelimitador. Mira:

printf %b\\n $(printf \
        "Fe\n\"w\"er\0'nu\t'll\\'s\0h    ere\0." |
_zedlmt | tee /dev/stderr)
    #output
\x46\x65\x0a\x22\x77\x22\x65\x72
\x27\x6e\x75\x09\x27\x6c\x6c\x27\x73
\x68\x20\x20\x20\x20\x65\x72\x65
\x2e
Fe
"w"er
'nu     'll's
h    ere
.

Tampoco hay citas sobre esa expansión, no importa si la citas o no. Esto se debe a que los valores de mordida no se separan, excepto la \nlínea de línea generada por cada vez que sedimprime una cadena. La división de palabras no se aplica. Y eso es lo que hace esto posible:

_pidenv() { ps -p $1 >/dev/null 2>&1 &&
        [ -z "${1#"$psrc"}" ] && . /dev/fd/3 ||
        cat <&3 ; unset psrc pcat
} 3<<STATE
        $( [ -z "${1#${pcat=$psrc}}" ] &&
        pcat='$(printf %%b "%s")' || pcat="%b"
        xeq="$(printf '\\x%x' "'=")"
        for x in $( _zedlmt </proc/$1/environ ) ; do
        printf "%b=$pcat\n" "${x%%"$xeq"*}" "${x#*"$xeq"}"
        done)
#END
STATE

La función anterior se utiliza _zedlmtpara ${pcat}una secuencia preparada de código de bytes para el abastecimiento del entorno de cualquier proceso que se pueda encontrar en /proc, o directamente .dot ${psrc}al mismo en el shell actual, o sin un parámetro, para mostrar una salida procesada de la misma al terminal como seto lo printenvharé. Todo lo que necesita es un $pid: cualquier/proc/$pid/environ archivo legible servirá.

Lo usas así:

#output like printenv for any running process
_pidenv $pid 

#save human friendly env file
_pidenv $pid >/preparsed/env/file 

#save unparsed file for sourcing at any time
_pidenv ${pcat=$pid} >/sourcable/env.save 

#.dot source any pid's $env from any file stream    
_pidenv ${pcat=$pid} | sh -c '. /dev/stdin'

#feed any pid's env in on a heredoc filedescriptor
su -c '. /dev/fd/4' 4<<ENV
    $( _pidenv ${pcat=$pid} )
ENV

#.dot sources any $pid's $env in the current shell
_pidenv ${psrc=$pid} 

Pero, ¿cuál es la diferencia entre ser amigable con los humanos y el sourcable ? Bueno, la diferencia es lo que hace que esta respuesta sea diferente a todas las demás aquí, incluida la otra. Cualquier otra respuesta depende de las citas de shell de una forma u otra para manejar todos los casos límite. Simplemente no funciona tan bien. Por favor créeme, lo he intentado. Mira:

_pidenv ${pcat=$$}
    #output
LC_COLLATE=$(printf %b "\x43")
GREP_COLOR=$(printf %b "\x33\x37\x3b\x34\x35")
GREP_OPTIONS=$(printf %b "\x2d\x2d\x63\x6f\x6c\x6f\x72\x3d\x61\x75\x74\x6f")
LESS_TERMCAP_mb=$(printf %b "\x1b\x5b\x30\x31\x3b\x33\x31\x6d")
LESS_TERMCAP_md=$(printf %b "\x1b\x5b\x30\x31\x3b\x33\x31\x6d")
LESS_TERMCAP_me=$(printf %b "\x1b\x5b\x30\x6d")
LESS_TERMCAP_se=$(printf %b "\x1b\x5b\x30\x6d")
LESS_TERMCAP_so=$(printf %b "\x1b\x5b\x30\x30\x3b\x34\x37\x3b\x33\x30\x6d")
LESS_TERMCAP_ue=$(printf %b "\x1b\x5b\x30\x6d")

NINGUNA cantidad de caracteres extravagantes o comillas contenidas puede romper esto porque los bytes de cada valor no se evalúan hasta el momento en que se obtiene el contenido. Y ya sabemos que funcionó como un valor al menos una vez: no es necesario analizar o proteger la cotización aquí porque se trata de una copia byte por byte del valor original.

La función primero evalúa los $varnombres y espera a que se completen las comprobaciones antes de .dotobtener el here-doc alimentado en el descriptor de archivo 3. Antes de obtenerlo, así es como se ve. Es infalible. Y POSIX portátil. Bueno, al menos el manejo de \ 0null es POSIX portable: el sistema de archivos / process es obviamente específico de Linux. Y es por eso que hay dos funciones.

mikeserv
fuente
3

Uso sourcey sustitución de procesos :

source <(sed -r -e 's/([^\x00]*)\x00/export \1\n/g' /proc/1/environ)

Dentro de poco:

. <(sed -r -e 's/([^\x00]*)\x00/export \1\n/g' /proc/1/environ)

Uso evaly sustitución de comandos :

eval `sed -r -e 's/([^\x00]*)\x00/export \1\n/g' /proc/1/environ`

los sed llamada se puede reemplazar con una awkllamada:

awk -vRS='\x00' '{ print "export", $0 }' /proc/1/environ

Pero no olvide que no borra ninguna variable de entorno que no esté en pid 1.

Pavel Šimerda
fuente
¿La exportación es redundante en la respuesta de @ fr00tyl00p? Porque si no, parece bastante importante
Dane O'Connor
Sí, se necesita la exportación. Lo arreglaré.
Pavel Šimerda
3
Todos estos comandos se ahogan con valores que contienen nuevas líneas y (dependiendo del comando) otros caracteres.
Gilles 'SO- deja de ser malvado'
Correcto. Mantendrá la respuesta como referencia de todos modos.
Pavel Šimerda
3

Vale la pena señalar que los procesos pueden tener variables de entorno que no son válidas Bash / Sh / * sh variables - POSIX recomienda pero no requiere que las variables de entorno tengan nombres coincidentes ^[a-zA-Z0-9_][a-zA-Z0-9_]*$ .

Para generar una lista de variables que son compatibles con shell desde el entorno de otro proceso, en Bash:

function env_from_proc {
  local pid="$1" skipped=( )
  cat /proc/"$pid"/environ | while read -r -d "" record
  do
    if [[ $record =~ ^[a-zA-Z_][a-zA-Z0-9_]*= ]]
    then printf "export %q\n" "$record"
    else skipped+=( "$record" )
    fi
  done
  echo "Skipped non-shell-compatible vars: ${skipped[@]%%=*}" >&2
}

Del mismo modo, para cargarlos:

function env_from_proc {
  local pid="$1" skipped=( )
  while read -r -d "" record
  do
    if [[ $record =~ ^[a-zA-Z_][a-zA-Z0-9_]*= ]]
    then export "$(printf %q "$record")"
    else skipped+=( "$record" )
    fi
  done < /proc/"$pid"/environ
  echo "Skipped non-shell-compatible vars: ${skipped[@]%%=*}" >&2
}

Este problema surge solo ocasionalmente, pero cuando lo hace ...

merienda
fuente
0

Creo que esto es POSIX portátil:

. <<ENV /dev/stdin
    $(sed -n 'H;${x;s/\(^\|\x00\)\([^=]*.\)\([^\x00]*\)/\2\x27\3\x27\n/gp}' \
       /proc/$pid/environ)
ENV

Pero @Gilles hace un buen punto: sedprobablemente manejará nulos, pero tal vez no. Así que también existe este método portátil POSIX (realmente lo creo esta vez) :

s=$$SED$$
sed 's/'\''/'$s'/;1s/^./'\''&/' </proc/"$$"/environ |
tr '\0' "'" |
sed 's/'\''/&\n&/g' |
sed '1d;$d;s/^\('\''\)\([^=]*.\)/\2\1/;s/'$s'/'\\\''/g'

Aún así, si tienes GNU sedsolo necesitas hacer:

sed -z 's/^[^=]*./&'\''/;s/$/'\''\n/' </proc/"$$"/environ

  #BOTH METHODS OUTPUT:

ingrese la descripción de la imagen aquí

Bueno, POSIX portable, excepto por el /dev/...que no está especificado, pero puede esperar que la sintaxis se comporte igual en la mayoría de los Unices.

Ahora, si esto tiene algo que ver con su otra pregunta , puede usarlo así:

nsenter -m -u -i -n -p -t $PID /bin/bash 5<<ENV --rcfile=/dev/fd/5 
    $(sed -z 's/^[^=]*./&'\''/;s/$/'\''\n/' </proc/"$$"/environ)
ENV

Here-doc es extremadamente útil porque evita que el shell se enrosque con cualquiera de las citas que trabajamos tan duro en el subshell y también nos proporciona una ruta confiable a un archivo.dot fuente en lugar de, nuevamente, un subshell o un shell variable. Otros aquí usan el bashism que funciona de la misma manera, solo que definitivamente es anónimo, mientras que POSIX solo especifica un para here-docs y, por lo tanto, puede ser cualquier tipo de archivo, aunque, en la práctica, generalmente es un archivo. ( por otro lado, utiliza anónimo para documentos aquí) . Sin embargo, lo desafortunado de la sustitución de procesos es que también depende de la shell, lo que podría ser un problema especialmente molesto si está trabajando .<(process substitution)|pipeioheretempdash,|pipesinit

Esto también funciona, por |pipessupuesto, pero luego pierde el entorno nuevamente al final cuando el |pipe'sestado se evapora con su subshell. Por otra parte, esto funciona:

sed '...;a\exec <>/dev/tty' /proc/$pid/environ | sh -i 

La seddeclaración en sí funciona manteniendo cada línea en la memoria hasta que llegue a la última, momento en el que realiza una cita de manejo de reemplazo global e inserta nuevas líneas cuando sea apropiado anclando en los nulos. Bastante simple en realidad.

En la dashimagen que verá, opté por evitar el \ mess y agregué la opción GNUespecífica . Pero eso es solo porque fue menos para escribir. Funciona de cualquier manera, como puedes ver en la imagen.-rsedzsh

Aquí está zsh:

ingrese la descripción de la imagen aquí

Y aquí está dashhaciendo lo mismo:

ingrese la descripción de la imagen aquí

Incluso los escapes de terminales salen indemnes:

ingrese la descripción de la imagen aquí

mikeserv
fuente
Esto no es POSIX-portable porque sed no es necesario para manejar bytes nulos. (Dicho esto, la portabilidad POSIX no es tan interesante para esta pregunta, porque solo se aplica a los sistemas que sí lo tienen /proc/PID/environ. Entonces, la pregunta es qué admite Solaris sed, o si Solaris lo ha hecho /proc/PID/environ, no solía hacerlo, pero estoy seguro detrás de la curva de las características de Solaris para que pueda ser hoy en día.)
Gilles 'SO- deja de ser malvado'
@Gilles No. Pero sedse requiere para manejar ascii hexadecimal, de los cuales el byte nulo es uno. Además, en realidad solo pensé si una forma mucho más fácil de hacer esto todavía.
mikeserv
No, POSIX dice que "Los archivos de entrada deben ser archivos de texto" (para sed y otras utilidades de texto) y define los archivos de texto como "archivo que contiene caracteres organizados en una o más líneas. Las líneas no contienen caracteres NUL (...) ". Y, por cierto, la \xNNsintaxis no es necesaria en POSIX, ni siquiera la \OOOsintaxis octal (en cadenas C y en awk, sí, pero no en expresiones regulares sed).
Gilles 'SO- deja de ser malvado'
@ Gilles tienes un punto. Miré por todas partes y no pude encontrar lo que pensé que podía antes. Entonces lo hice de manera diferente. Editando ahora.
mikeserv
Por lo que puedo decir, Solaris no tiene /proc/PID/environdespués de todo (tiene varias otras entradas similares a Linux /proc/PID, pero no environ). Entonces, una solución portátil no necesita ir más allá de las herramientas de Linux, después de todo, lo que significa GNU sed o BusyBox sed. Ambos son compatibles \x00con una expresión regular, por lo que su código es tan portátil como sea necesario (pero no POSIX). Sin embargo, es demasiado complejo.
Gilles 'SO- deja de ser malvado'
-2
eval \`(cat /proc/1/environ; echo) | tr '\000' '\n'\`
Martin Drautzburg
fuente
1
Esto no funciona si algún valor contiene algún carácter especial de shell.
Gilles 'SO- deja de ser malvado'