Como fonte de variáveis ​​de ambiente de outro processo?

24

Se eu examinar /proc/1/environ, posso ver uma sequência delimitada por bytes nulos das 1variáveis ​​de ambiente do processo . Eu gostaria de trazer essas variáveis ​​para o meu ambiente atual. Existe uma maneira fácil de fazer isso?

A procpágina de manual fornece um trecho que ajuda a imprimir cada variável de ambiente, linha por linha (cat /proc/1/environ; echo) | tr '\000' '\n'. Isso me ajuda a verificar se o conteúdo está correto, mas o que realmente preciso fazer é originar essas variáveis ​​na minha sessão atual do bash.

Como faço isso?

Dane O'Connor
fonte

Respostas:

23

A seguir, converteremos cada variável de ambiente em uma exportinstrução, citada corretamente para leitura em um shell (porque LS_COLORS, por exemplo, é provável que tenha ponto-e-vírgula), e as origens.

[A printfem /usr/bin, infelizmente, geralmente não suporta %q, por isso precisamos chamar o que se construiu em bash.]

. <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ)
Mark Plotnick
fonte
Sugiro . <(xargs -0 bash -c 'printf "export %q\n" "$@"' -- < /proc/nnn/environ), que também manipulará variáveis ​​com aspas.
John Kugelman apoia Monica
@JohnKugelman Muito obrigado pela melhoria, usando em "$@"vez de '{}'. Para aqueles que se perguntam sobre o --argumento em sua resposta aprimorada: argumentos posicionais a bash -c command_stringsão atribuídos a partir de $0, enquanto "$@"expande para incluir argumentos a partir de $1. O argumento --é atribuído a $0.
precisa saber é o seguinte
10

Em bashvocê pode fazer o seguinte. Isso funcionará para todo o conteúdo possível das variáveis ​​e evita eval:

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

Isso declarará as variáveis ​​de leitura como variáveis ​​do shell no shell em execução. Para exportar as variáveis ​​para o ambiente shell em execução:

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

Nesta resposta, assumo um sistema em que /proc/$pid/environretorna o ambiente do processo com o PID especificado, com bytes nulos entre as definições de variáveis. ( Então Linux, Cygwin ou Solaris (?) ).

Zsh

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

(O zsh é bastante simples: um redirecionamento de entrada sem comando <FILE é equivalente a cat FILE. A saída da substituição de comando passa por expansão de parâmetro com os sinalizadores que ps:\000: significam "divisão em bytes nulos" e @que "se tudo estiver entre aspas duplas, então trate cada elemento da matriz como um campo separado "(generalização "$@").)

Bash, mksh

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

(Nessas shells, um delimitador vazio transmitido readresulta em bytes nulos sendo separadores. Eu uso PWDcomo um nome de variável temporário para evitar atrapalhar outra variável que pode acabar sendo importada. Embora você também pudesse importar tecnicamente PWD, ele só ficaria parado até o próximo cd.)

POSIX

A portabilidade POSIX não é tão interessante para esta questão, porque se aplica apenas aos sistemas que possuem /proc/PID/environ. Portanto, a questão é o que o Solaris sed suporta - ou se o Solaris tem /proc/PID/environ, mas não estou acostumado, mas estou muito atrasado nos recursos do Solaris, o que pode acontecer nos dias de hoje. No Linux, os utilitários GNU e o BusyBox são seguros para nulos, mas com ressalvas.

Se insistirmos na portabilidade do POSIX, nenhum dos utilitários de texto POSIX será necessário para lidar com bytes nulos, portanto, isso é difícil. Aqui está uma solução que assume que o awk suporta um byte nulo como o delimitador de registro (nawk e gawk, assim como o BusyBox awk, mas o mawk não).

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

O BusyBox awk (que é a versão comumente encontrada nos sistemas Linux embarcados) suporta bytes nulos, mas não está definido RScomo "\0"em um BEGINbloco e nem a sintaxe da linha de comando acima; no entanto, suporta -v 'RS="\0"'. Eu não investiguei o porquê, isso parece um bug na minha versão (Debian wheezy).

(Quebre todas as linhas registros separados por nulo entre aspas simples "\047", depois de escapar das aspas simples dentro dos valores.)

Ressalvas

Lembre-se de que qualquer uma dessas opções pode tentar definir variáveis ​​somente leitura (se o seu shell tiver variáveis ​​somente leitura).

Gilles 'SO- parar de ser mau'
fonte
Finalmente voltei a isso. Eu descobri um meio infalível de fazer isso ou qualquer manipulação nula em todas as conchas que eu conheço de maneira simples. Veja minha nova resposta.
precisa
6

Eu voltei e voltei com isso. Fiquei frustrado com a portabilidade de bytes nulos. Não me ocorreu bem que não havia uma maneira confiável de lidar com eles em uma concha. Então eu continuei olhando. A verdade é que encontrei várias maneiras de fazer isso, das quais apenas duas são mencionadas na minha outra resposta. Mas os resultados foram pelo menos duas funções de shell que funcionam assim:

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

Primeiro vou falar sobre a \0delimitação. Na verdade, é muito fácil de fazer. Aqui está a função:

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

Basicamente, odleva stdine grava em stdoutcada byte que recebe em hexadecimal um por linha.

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

Aposto que você pode adivinhar qual é o \0nullcerto? Escrito assim, é fácil lidar com qualquer um sed . sedapenas salva os dois últimos caracteres em cada linha até encontrar um nulo; nesse ponto, substitui as novas linhas intermediárias pelo printfcódigo de formato amigável e imprime a string. O resultado é uma \0nullmatriz delimitada de cadeias de bytes hexadecimais. Veja:

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
.

Eu canalizei o acima para teeque você pudesse ver a saída do comando susbstitution e o resultado do printfprocessamento. Espero que você observe que o subshell também não é citado, mas printfainda é dividido apenas no \0nulldelimitador. Veja:

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
.

Também não há aspas nessa expansão - não importa se você citou ou não. Isso ocorre porque os valores da mordida são apresentados sem separação, exceto pela linha de \new gerada para cada vez que seduma string é impressa. A divisão de palavras não se aplica. E é isso que torna isso possível:

_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

A função acima usa _zedlmtpara ${pcat}um fluxo preparado de código de bytes para a origem do ambiente de qualquer processo que possa ser encontrado /procou diretamente para .dot ${psrc}o mesmo no shell atual, ou sem um parâmetro, para exibir uma saída processada do mesmo para o terminal, como setou printenvvontade. Tudo que você precisa é de $pid- qualquer/proc/$pid/environ arquivo legível fará.

Você o usa assim:

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

Mas qual é a diferença entre humano amigável e fonte ? Bem, a diferença é que faz com que essa resposta seja diferente de todas as outras aqui - incluindo a minha outra. Qualquer outra resposta depende do shell citar de uma maneira ou de outra para lidar com todos os casos extremos. Simplesmente não funciona tão bem. Por favor, acredite em mim - eu tentei. Veja:

_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")

NENHUMA quantidade de caracteres descolados ou citações contidas pode quebrar isso porque os bytes de cada valor não são avaliados até o instante em que o conteúdo é originado. E já sabemos que funcionou como um valor pelo menos uma vez - não há proteção de análise ou cotação necessária aqui, porque esta é uma cópia de byte a byte do valor original.

A função primeiro avalia os $varnomes e aguarda a conclusão das verificações antes .dotde fornecer o documento aqui, alimentado pelo descritor de arquivo 3. Antes de fornecê-lo, é assim que parece. É à prova de idiotas. E POSIX portátil. Bem, pelo menos a manipulação \ 0null é POSIX portátil - o sistema de arquivos / process é obviamente específico do Linux. E é por isso que existem duas funções.

mikeserv
fonte
3

Usando sourcee substituindo processo :

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

Em breve:

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

Usando evale substituição de comando :

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

A sedchamada pode ser substituída por uma awkchamada:

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

Mas não esqueça que ele não limpa as variáveis ​​de ambiente que não estão no pid 1.

Pavel Šimerda
fonte
A exportação é redundante na resposta da @ fr00tyl00p? Porque, se não, parece muito importante
Dane O'Connor
Sim, a exportação é necessária. Eu vou consertar.
Pavel Šimerda
3
Todos esses comandos são bloqueados por valores que contêm novas linhas e (dependendo do comando) outros caracteres.
Gilles 'SO- deixa de ser mau'
Corrigir. Manterá a resposta para referência de qualquer maneira.
Pavel Šimerda
3

Vale notar que os processos podem ter variáveis ​​de ambiente que não são válidas variáveis ​​Bash / Sh / * sh - o POSIX recomenda, mas não exige que as variáveis ​​de ambiente tenham nomes correspondentes ^[a-zA-Z0-9_][a-zA-Z0-9_]*$.

Para gerar uma lista de variáveis ​​compatíveis com shell do ambiente de outro processo, no 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
}

Da mesma forma, para carregá-los:

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
}

Esse problema surge apenas ocasionalmente, mas quando ocorre ...

solidsnack
fonte
0

Eu acho que isso é POSIX portátil:

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

Mas o @Gilles faz um bom argumento - sedprovavelmente manipulará nulos, mas talvez não. Então tem isso (eu realmente acho que dessa vez) método realmente portátil POSIX:

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

Ainda assim, se você possui o GNU, sedbasta fazer o seguinte:

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

  #BOTH METHODS OUTPUT:

insira a descrição da imagem aqui

Bem, o POSIX portable, exceto o /dev/...que não está especificado, mas você pode esperar que a sintaxe se comporte da mesma maneira na maioria dos Unices.

Agora, se isso tem algo a ver com sua outra pergunta , você pode usá-lo assim:

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

O documento aqui é extremamente útil, pois evita que o shell se estrague com qualquer uma das citações que trabalhamos tão duro para manipular no subshell e também fornece um caminho confiável para um arquivo.dot fonte, em vez de, novamente, um subshell ou shell variável. Outros aqui usam o bashismo que funciona da mesma maneira - apenas é definitivamente um anônimo, enquanto o POSIX especifica apenas um para here-docs e, portanto, pode ser qualquer tipo de arquivo, embora, na prática, seja geralmente um arquivo. ( por outro lado, usa informações anônimas<(process substitution)|pipeioheretempdash,|pipes para documentos aqui) . O lamentável sobre a substituição de processos, no entanto, é que também depende do shell - o que pode ser um problema especialmente irritante se você estiver trabalhando init.

Isso também funciona |pipes, é claro, mas você perde o ambiente novamente no final, quando o |pipe'sestado evapora com seu subshell. Então, novamente, isso funciona:

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

A sedinstrução em si funciona mantendo todas as linhas na memória até chegar à última, quando executa uma manipulação global de substituição, citando e inserindo novas linhas, quando apropriado, ancorando nos nulos. Bastante simples, na verdade.

Na dashfoto, você verá que optei por evitar a \ mess e adicionei a opção GNUespecífica -ra sed. Mas isso é apenas porque era menos para digitar. Funciona de qualquer maneira, como você pode ver na zshimagem.

Aqui está zsh:

insira a descrição da imagem aqui

E aqui está dasha mesma coisa:

insira a descrição da imagem aqui

Até as fugas do terminal passam ilesas:

insira a descrição da imagem aqui

mikeserv
fonte
Isso não é portátil para POSIX, porque sed não é necessário para manipular bytes nulos. (Dito isto, a portabilidade do POSIX não é tão interessante para esta questão, porque se aplica apenas a sistemas que possuem /proc/PID/environ. Portanto, a questão é o que o Solaris sed suporta - ou se o Solaris possui /proc/PID/environ, não costumava, mas estou por trás da curva nos recursos do Solaris, para que possa nos dias de hoje.)
Gilles 'SO- stop be evil'
@Gilles No. Mas sedé necessário lidar com ascii hexadecimal, dos quais o byte nulo é um. Além disso, na verdade, eu pensei apenas se uma maneira muito mais fácil de fazer isso ainda.
mikeserv
Não, o POSIX diz que “Os arquivos de entrada devem ser arquivos de texto” (para sed e outros utilitários de texto) e define arquivos de texto como “arquivo que contém caracteres organizados em uma ou mais linhas. As linhas não contêm caracteres NUL (…) ”. E, a propósito, a \xNNsintaxe não é necessária no POSIX, nem mesmo na \OOOsintaxe octal (nas seqüências de caracteres C e no awk, sim, mas não nos regexps sed).
Gilles 'SO- stop be evil'
@ Gilles você tem razão. Eu olhei por todo o lado e não consegui encontrar o que achava que poderia encontrar antes. Então eu fiz diferente. Editando agora.
mikeserv
Até onde eu sei, o Solaris não possui, /proc/PID/environafinal (ele tem várias outras entradas semelhantes ao Linux /proc/PID, mas não environ). Portanto, uma solução portátil não precisa ir além das ferramentas Linux, o que significa GNU sed ou BusyBox sed. Ambos suportam \x00em uma regexp, portanto, seu código é tão portátil quanto necessário (mas não POSIX). É excessivamente complexo.
Gilles 'SO- stop be evil'
-2
eval \`(cat /proc/1/environ; echo) | tr '\000' '\n'\`
Martin Drautzburg
fonte
11
Isso não funciona se qualquer valor contiver algum caractere especial do shell.
Gilles 'SO- stop be evil'