Entendendo as variáveis ambientais em diferentes contextos

6

Tentando entender o comportamento do ambiente no Linux (o Ubuntu 13.04 concretamente), encontrei diferentes situações em que a configuração de variáveis de ambiente é usada ou definida para / em diferentes contextos. Por exemplo, se eu verificar, locale , recebo:

$ locale
LANG=en_US.UTF-8
LANGUAGE=es_ES:es_HN:es_EC:en
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC=es_ES.UTF-8
// more output

Mas, se eu encontrar, por exemplo, LC_CTYPE usando env | grep "LC_CTYPE" , ele não enviará nenhuma saída. Em geral, locale me mostra 13 LC_* variables e env apenas nove:

$ locale | grep "LC_*" | wc -l
13
$ env | grep "LC_*" | wc -l
9

Outra variável com "natureza" diferente é PS1 . Por exemplo:

$ env | grep "PS1" # No output, but...
$ set | grep "PS1" | head -n 1
PS1=$'\[\033[1;33m\][\t][\W]203\[\033[0m\] '

e, é claro, PS1 é uma variável bem definida em meu ambiente atual, já que vejo meu prompt alterado de acordo.

Outra maneira de visualizar variáveis de ambiente em outro contexto é por meio de strace . É um programa que permite que você veja o que está acontecendo quando você executa um programa. Amostra:

$ strace -v ./a.out # a.out is a common Hello World, made in C.
execve("./a.out", ["./a.out"], ["LC_PAPER=es_ES.UTF-8", ...]) = 0
brk(0)
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
# etc, etc
write(1, "Hello World\n", 12Hello World
)           = 12
exit_group(0)                           = ?

A primeira coisa que o shell faz ao executar um programa é a chamada para execve , que realmente chama um programa. Seu primeiro argumento é o programa sendo chamado, o segundo é o argv params do programa chamado e o terceiro parâmetro são as variáveis de ambiente.

Nesse terceiro parâmetro, por exemplo, não aparece PS1 ou LC_TYPE .

Em geral, as variáveis que aparecem em env ou set aparecem na lista de variáveis de ambiente enviadas para execve . Algumas variáveis locale aparecem em env ou set , mas outras não ( LC_TYPE , LC_COLLATE e LC_MESSAGE , bem como LC_ALL , mas com um valor vazio). Por fim, outras variáveis não são definidas em env , embora tenham um efeito visível ( PS1 ), conforme refletido por set .

O que está acontecendo aqui? Quais são as diferenças entre env , set (sem argumentos), locale (obviamente respeitando apenas as variáveis de localidade)?

    
por Peregring-lk 06.04.2014 / 16:00

3 respostas

13

O principal problema aqui - que explica por que, por exemplo, $PS1 não é relatado por env - é que env está relatando de um ambiente não interativo . Os processos são executados a partir de uma bifurcação do seu shell interativo , mas há uma sutileza envolvida em como o ambiente é definido: na verdade, ele é herdado por meio de um conjunto de variáveis externas nativas do nível C para todos os processos exec() d veja man environ ). Aqui está uma ilustração:

#include <stdio.h>

extern char **environ;

int main (void) {
    int i;
    for (i = 0; environ[i] != NULL; i++) {
        printf("%s\n", environ[i]);
    }
    return 0;
}      

O interessante é que, se você compilar e executar, o conteúdo de **environ corresponderá exatamente ao reportado por env :

$ gcc test.c
$ ./a.out > aout.txt
$ env > env.txt
$ diff env.txt aout.txt
68c68
< _=/bin/env
---
> _=./a.out

A única diferença é o nome do executável. Então, de onde vem **environ e por que não contém, por exemplo, $PS1 ?

A explicação fundamental é que o processo é sempre criado como filhos de outros processos e eles herdam **environ , mas PS1 nunca fez parte dele. Na inicialização, um shell pode fornecer variáveis a partir de locais padrão, e esses locais diferem dependendo se o shell é interativo ou não; veja INVOCATION em man bash . Um aspecto disso é que:

PS1 is set [...] if bash is interactive, allowing a shell script or a startup file to test this state.

Agora, observe em /etc/bashrc algo assim:

# are we an interactive shell?
if [ "$PS1" ]; then

Qual é o local onde o prompt real (de fantasia) está configurado e nem o valor inicial de $PS1 nunca foi export ed. O valor inicial foi criado pelo shell na chamada porque era interativo e, em seguida, originou esse arquivo, mas PS1 não foi colocado em **environ . Você pode ver isso se você executar:

#!/bin/sh

echo $PS1

Nada - mesmo que você tenha echo $PS1 em seu shell interativo definido. Isso ocorre porque o **environ do #!/bin/sh executado é o mesmo do shell interativo pai, mas NÃO contém PS1 . Isso implica que cada shell usa uma tabela interna de variáveis globais separadas, mas originalmente preenchidas, de **environ (isso é confuso, pois significa que **environ não inclui muitas coisas referidas como environment variables ).

O conteúdo de **environ está em /proc/[PID]/environ e, se você verificar que o seu shell interativo atual, cat /proc/$BASHPID/environ , verá PS1 não está lá.

Mas como as coisas entram no "environ"?

A resposta simples é, via chamadas de sistema. Por exemplo, se lançarmos algumas coisas no programa C de exemplo anterior:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

extern char **environ;

int main (void) {
    int i;
    if (putenv("MYFOO=whatbar?")) {
        fprintf(stderr, "putenv() failed: %s\n", strerror(errno));
        exit(1);
    }

    for (i = 0; environ[i] != NULL; i++) {
        printf("%s\n", environ[i]);
    }

    return 0;
}           

MYFOO=whatbar? estará na saída (consulte man putenv ). Como o shell cria processos por fork() ing (que duplica a pilha de memória do pai) e chamando execv() (que passa no **environ duplicado), podemos ver um mecanismo pelo qual variáveis de ambiente podem ser export ed para processos filhos.

Se você lançar um fork() nesse exemplo, verá que esse é o caso, e (para reiterar), esse processo de fork'ing e potencialmente exec'ing é como processos filhos são criados e herdam **environ de seus ancestrais. exec chamadas substituem a imagem do processo, mas conforme man execv e man environ (nb. algumas versões da primeira não se referem a isso), **environ é passado pelo sistema.

Veja um fork literal e exec de /usr/bin/env com MYFOO=whatbar? exportados via putenv() :

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

extern char **environ;

int main (void) {
    pid_t pid;

    if (putenv("MYFOO=whatbar?")) {
        fprintf(stderr, "putenv() failed: %s\n", strerror(errno));
        exit(1);
    }

    pid_t pid = fork();
    if (!pid) execl("/usr/bin/env", "env", NULL);

    return 0;
}         

Então, onde estão as coisas que não estão no "environ"?

São dados privados de uma determinada instância da shell. O Bash mostrará este + o material do ambiente herdado via set sem argumentos. Observe que esta saída também inclui funções originadas.

But, if I find, for example, LC_CTYPE using env | grep "LC_CTYPE", it sends no output. In general, locale shows me 13 LC_* variables and env only nine:

Eu não obtenho nenhuma variável LC_ de env (apenas LANG ) mas 13 de locale . Eu presumo que sejam variáveis definidas por uma chamada locale e não exportadas; o fato de você obter algum de env talvez reflita um erro ingênuo em alguma configuração em algum lugar.

    
por 06.04.2014 / 16:22
4

O shell conhece dois tipos de variáveis:

  1. variáveis "internas" que são conhecidas apenas pelo shell (e por sub-unidades)

  2. variáveis exportadas, as "oficiais" que são vistas por execve e, portanto, por env . O shell embutido export mostra as variáveis exportadas.

Se você executar

export PS1

e repita

env | grep "PS1"

então você vê. As variáveis podem ser exportadas durante a criação ( export foo=bar em vez de foo=bar ), elas podem ser exportadas automaticamente na criação ou modificação ( set -a ), elas podem ser exportadas posteriormente ( var=foo; ...; export var ) e podem ser "não exportadas" ( export -n var ).

Se o shell criar subshells "reais" (por a|b , (a;b) , $(a) e assim por diante), ele manterá várias variáveis não exportadas para evitar o caos.

    
por 06.04.2014 / 16:21
4

A saída do comando locale não é uma lista de variáveis de ambiente do ambiente atual. É uma exibição das configurações de localidade efetivas do processo (que é influenciada em parte por determinadas variáveis de ambiente) e é apresentada no mesmo formato key=value que o comando env usa.

Você pode ver a origem da implementação do comando eglibc locale aqui: link

    
por 07.04.2014 / 00:40