Por que não consigo imprimir uma variável que vejo na saída de env?

9

Estou interessado em definir variáveis ​​ambientais de uma instância de shell de outra. Então eu decidi fazer alguma pesquisa. Depois de ler um número de perguntas sobre isso eu decidi testá-lo.

Eu criei duas conchas A e B (PID 420), ambas em execução zsh. A partir do shell, AI executou o seguinte.

sudo gdb -p 420
(gdb) call setenv("FOO", "bar", 1)
(gdb) detach

No shell B, quando executo env, posso ver que a variável FOO está realmente definida com um valor de bar. Isso me faz pensar que o FOO foi inicializado com sucesso no ambiente do shell B. No entanto, se eu tentar imprimir o FOO, recebo uma linha vazia, indicando que ela não está definida. Para mim, parece que há uma contradição aqui.

Isso foi testado no meu próprio sistema Arch GNU / Linux e em uma VM do Ubuntu. Também testei isso em bashque a variável nem apareceu em env. Isso, apesar de decepcionante para mim, faz sentido se o shell armazena em cache uma cópia de seu ambiente no momento da desova e a utiliza apenas (o que foi sugerido em uma das perguntas vinculadas). Isso ainda não responde por que zshpode ver a variável.

Por que a saída de echo $FOOvazia?


EDITAR

Após a entrada nos comentários, decidi fazer um pouco mais de teste. Os resultados podem ser vistos nas tabelas abaixo. Na primeira coluna está o shell no qual a FOOvariável foi injetada. A primeira linha contém o comando cuja saída pode ser vista abaixo dela. A variável FOOfoi injectado usando: sudo gdb -p 420 -batch -ex 'call setenv("FOO", "bar", 1)'. Os comandos específicos do zsh: zsh -c '...'também foram testados usando o bash. Os resultados foram idênticos, sua saída foi omitida por brevidade.

Arch GNU / Linux, zsh 5.3.1, bash 4.4.12 (1)

|      |  env | grep FOO  | echo $FOO |  zsh -c 'env | grep FOO'  |  zsh -c 'echo $FOO'  |         After export FOO          |
|------|------------------|-----------|---------------------------|----------------------|-----------------------------------|
| zsh  |  FOO=bar         |           | FOO=bar                   | bar                  | No Change                         |
| bash |                  | bar       |                           |                      | Value of FOO visible in all tests |

Ubuntu 16.04.2 LTS, zsh 5.1.1, bash 4.3.48 (1)

|      |  env | grep FOO  | echo $FOO |  zsh -c 'env | grep FOO'  |  zsh -c 'echo $FOO'  |         After export FOO          |
|------|------------------|-----------|---------------------------|----------------------|-----------------------------------|
| zsh  |  FOO=bar         |           | FOO=bar                   | bar                  | No Change                         |
| bash |                  | bar       |                           |                      | Value of FOO visible in all tests |

O exposto acima parece implicar que os resultados são agnósticos na distribuição. Isso não me diz muito mais do que zshe bashlida com a configuração de variáveis ​​de maneira diferente. Além disso, export FOOpossui um comportamento muito diferente nesse contexto, dependendo do shell. Esperançosamente, esses testes podem deixar algo claro para outra pessoa.

rlf
fonte
O que acontece se você fizer um zsh -c 'echo $FOO'(use aspas simples!)? Você pode ver isso então?
User1934428
O valor correto é impresso em um novo sub shell (testado também para o bash child). Claramente, o ambiente é persistente de alguma forma, pois a criança pode herdá-lo, mas por que os pais não o respeitam?
Rlf 11/07
3
Isso foi o que eu pensei. Eu acho que o shell tem em algum lugar uma tabela de símbolos de variáveis, algumas delas são marcadas como "exportadas", o que significa que, ao abrir um subshell, elas são colocadas no ambiente do processo filho. Inicialmente (quando o shell é iniciado), as variáveis ​​do ambiente naquele momento são copiadas na tabela de símbolos (é claro também como variáveis ​​"exportadas"). Quando você altera o ambiente, o shell não é notado para atualizar sua tabela de símbolos - mas processos filhos (como env) veem o ambiente modificado.
user1934428
2
Eu testei no Ubuntu 16.04 com zsh 5.1.1 e bash 4.3.48 (1) e parece que definir uma variável de ambiente zshno GDB não a torna visível como uma variável de shell, mas faz com que ela seja passada para processos filho (como você observou), ao definir um para bash o torna visível como uma variável de shell, mas não faz com que ela seja passada para processos filhos! Parece que o zsh e o bash usam estratégias diferentes para gerenciar variáveis, com o zsh que rastreia variáveis ​​que não são de ambiente e o bash armazena tudo em seu ambiente, que é higienizado ao iniciar um filho (sem sub-shell).
Eliah Kagan
@EliahKagan, interessante; você deve postar isso como resposta. Gostaria também de saber se ele faz a diferença se você executar export FOOem bash?
Curinga

Respostas:

2

A maioria dos shells não usa a getenv()/ setenv()/ putenv()API.

Na inicialização, eles criam variáveis ​​de shell para cada variável de ambiente. Eles serão armazenados em estruturas internas que precisam carregar outras informações, como se a variável é exportada, somente leitura ... Eles não podem usar as bibliotecas environpara isso.

Da mesma forma, e por essa razão, eles não vão usar execlp(), execvp()para executar comandos, mas chamar a execve()chamada de sistema diretamente, o cálculo da envp[]matriz com base na lista de suas variáveis exportadas.

Portanto, no seu caso gdb, você precisará adicionar uma entrada à tabela interna de variáveis ​​dos shells ou possivelmente chamar a função correta que faria com que ele interpretasse um export VAR=valuecódigo para atualizar a tabela por si só.

Quanto ao porquê você vê uma diferença entre bashe zshquando você chamar setenv()em gdb, eu suspeito que é porque você está chamando setenv()antes dos inicializa shell, por exemplo, ao entrar main().

Você notará bash's main()é ' int main(int argc, char* argv[], char* envp[])(e bashmapeia as variáveis ​​desses ambientes envp[]) enquanto zsh's é int main(int argc, char* argv[])e zshobtém as variáveis' environ. setenv()modifica, environmas não pode modificar envp[]no local (somente leitura em vários sistemas, bem como as seqüências de caracteres para as quais esses ponteiros apontam).

De qualquer forma, após a leitura do shell environna inicialização, o uso setenv()seria ineficaz, pois o shell não usa mais environ(ou getenv()) posteriormente.

Stéphane Chazelas
fonte