Atribuições variáveis ​​afetam o shell em execução atual

8

Enquanto escrevia algum código, descobri que esta linha:

$ TZ="America/Los_Angeles"       date; echo "$TZ"
Thu Dec 24 14:39:15 PST 2015

Fornece corretamente o tempo real em "Los Angeles" e que o valor da variável TZnão é retido. Tudo como deveria ser esperado.

No entanto, com esta linha, que eu costumava expandir alguns formatos até a data, e que basicamente executa a mesma coisa, mantém o valor de TZ:

TZ="America/Los_Angeles" eval  date; echo "$TZ"
Thu Dec 24 14:41:34 PST 2015
America/Los_Angeles

Após vários testes, descobri que isso acontece apenas em algumas conchas. Isso acontece no dash, ksh, mas não no bash ou zsh.

Q's

As perguntas são:

  • Por que o valor de TZ está sendo retido no shell atual?
  • Como isso poderia ser evitado / controlado (se possível)?

Adicional.

Eu executei testes em várias conchas com estas duas linhas:

myTZ="America/Los_Angeles"
unset TZ; { TZ="$myTZ"      date; } >/dev/null; echo -n "  direct $TZ"
unset TZ; { TZ="$myTZ" eval date; } >/dev/null; echo    "  evaled $TZ"

E isso resulta:

/bin/ash        :   direct   evaled America/Los_Angeles
/bin/dash       :   direct   evaled America/Los_Angeles
/bin/sh         :   direct   evaled America/Los_Angeles
/bin/bash       :   direct   evaled
/bin/ksh93      :   direct   evaled America/Los_Angeles
/bin/lksh       :   direct   evaled America/Los_Angeles
/bin/mksh       :   direct   evaled America/Los_Angeles
/bin/zsh        :   direct   evaled
/bin/zsh4       :   direct   evaled 

O valor TZ afeta o shell em execução em todos os shells, exceto bash e zsh.


fonte

Respostas:

6

Como você descobriu, esse é um comportamento específico. Mas isso também faz sentido.

O valor é retido no ambiente do shell pela mesma razão que o valor de outras variáveis ​​de ambiente são retidas por outros comandos quando você prefixa as definições nas linhas de comando - você está definindo as variáveis ​​no ambiente.

Os componentes especiais internos geralmente são a variedade mais intrínseca em qualquer shell - evalé essencialmente um nome acessível para o analisador do shell, setrastreia e configura opções e parâmetros do shell, return/ break/ continuetrigger do fluxo de controle do loop, trapmanipula sinais, execabre / fecha arquivos. Todos esses são utilitários fundamentais - e normalmente são implementados com invólucros quase imperceptíveis sobre a carne e as batatas da casca.

A execução da maioria dos comandos envolve algum ambiente em camadas - um ambiente de subcamadas (que não necessariamente precisa ser um processo separado) - que você não obtém ao chamar os componentes internos especiais. Portanto, quando você define o ambiente para um desses comandos, define o ambiente para o seu shell. Porque eles basicamente representam sua concha.

Mas eles não são os únicos comandos que mantêm o ambiente dessa maneira - as funções também fazem o mesmo. E os erros se comportam de maneira diferente para os built-ins especiais - tente cat <doesntexiste tente exec <doesntexistou mesmo apenas : <doesntexiste enquanto o catcomando reclamar, o execou :matará um shell POSIX. O mesmo se aplica aos erros de expansão na linha de comando. Eles são o loop principal , basicamente.

Esses comandos não precisam reter o ambiente - alguns shells envolvem seus internos com mais força do que outros, expõem menos a funcionalidade principal e adicionam mais buffer entre o programador e a interface. Essas mesmas conchas também tendem a ser um pouco mais lentas que outras. Definitivamente, eles exigem muitos ajustes fora do padrão para fazê-los se adequar às especificações. De qualquer forma, não é como se isso fosse uma coisa ruim :

fn(){ bad_command || return=$some_value return; }

Esse material é fácil . De que outra forma você preservaria o retorno de bad_commandforma simples, sem precisar definir um monte de ambiente extra e ainda assim executar tarefas condicionalmente?

arg=$1 shift; x=$y unset y

Esse tipo de coisa também funciona. As trocas no local são mais simples.

IFS=+  set -- "$IFS" x y z
x="$*" IFS=$1 shift
echo "${x#"$IFS"}" "$*"

+x+y+z x y z

...ou...

expand(){
    PS4="$*" set -x "" "$PS4" 
    { $1; }  2>&1
    PS4=$2   set +x
}   2>/dev/null

x='echo kill my computer; $y'
y='haha! just kidding!' expand "${x##*[\`\(]*}"

... é outro que eu gosto de usar ...

echo kill my computer; haha! just kidding!
mikeserv
fonte
@BinaryZebra - mas o ponto é que eles não funcionam de maneira diferente - quando você define variáveis ​​para algum outro comando, elas persistem no ambiente do outro executável. quando você define variáveis ​​no ambiente do seu shell, elas também persistem.
mikeserv
3

Acontece que existe uma razão muito específica para esse comportamento.
A descrição do que acontece é um pouco mais longa.

Somente atribuições.

Uma linha de comando feita (apenas) de atribuições definirá as variáveis ​​para este shell.

$ unset a b c d
$ a=b c=d
$ echo "<$a::$c>"
<b::d>

O valor dos vars atribuídos será retido.

Comando externo.

As atribuições antes de um comando externo definem variáveis ​​apenas para esse shell:

$ unset a b c d
$ a=b c=d bash -c 'echo "one:|$c|"'; echo "two:<$c>"
one:|d|
two:<>

E eu quero dizer "externo" como qualquer comando que precise ser pesquisado no PATH.

Isso também se aplica a embutidos normais (como cd, por exemplo):

$ unset a b c d; a=b c=d cd . ; echo "<$a::$c>"
<::>

Até aqui tudo é como é normalmente esperado.

Built-Ins especiais.

Mas para embutidos especiais, o POSIX exige que os valores sejam definidos para esse shell .

  1. As atribuições variáveis ​​especificadas com utilitários embutidos especiais permanecem em vigor após a conclusão da incorporação.
$ sh -c 'unset a b c d; a=b c=d export f=g ; echo "<$a::$c::$f>"'
<b::d::g>

Estou usando uma chamada para shassumir que shé um shell compatível com POSIX.

Isso não é algo que geralmente é usado.

Isso significa que as atribuições colocadas na frente de qualquer uma dessas listas de embutidos especiais manterão os valores atribuídos no presente shell em execução:

break : continue . eval exec exit export 
readonly return set shift times trap unset

Isso acontecerá se um shell funcionar conforme a especificação POSIX.

Conclusão:

É possível definir variáveis ​​para apenas um comando, qualquer comando, certificando-se de que o comando não seja um built-in especial. O comando commandé um builtin regular. Diz apenas ao shell para usar um comando, não uma função. Esta linha funciona em todos os shells (exceto ksh93):

$ unset a b c d; a=b c=d command eval 'f=g'; echo "<$a::$c::$f>"
<::::g>

Nesse caso, os vars a e b são definidos para o ambiente do comando command e descartados depois disso.

Em vez disso, isso manterá os valores atribuídos (exceto bash e zsh):

$ unset a b c d; a=b c=d eval 'f=g'; echo "<$a::$c::$f>"
<b::d::g>

Observe que a atribuição após avaliação é única entre aspas para protegê-la de expansões indesejadas.

Portanto: para colocar variáveis ​​no ambiente de comando, use command eval:


fonte