Um shell script pode definir variables ​​de ambiente do shell de chamada?

Eu estou tentando escrever um script de shell que, quando executado, irá definir algumas variables ​​de ambiente que permanecerão definidas no shell do chamador.

setenv FOO foo 

em csh / tcsh ou

 export FOO=foo 

em sh / bash, configure-o apenas durante a execução do script.

Eu já sei disso

 source myscript 

executará os comandos do script em vez de lançar um novo shell, e isso pode resultar na configuração do ambiente de “chamador”.

Mas aqui está o problema:

Eu quero esse script para ser chamado de bash ou csh. Em outras palavras, eu quero que os usuários de ambos os shell possam executar meu script e ter o ambiente do shell alterado. Portanto, ‘source’ não funcionará para mim, já que um usuário executando csh não pode criar um script bash, e um usuário que esteja executando o bash não pode fornecer um script csh.

Existe alguma solução razoável que não envolve a necessidade de escrever e manter duas versões no script?

    Seu processo de shell tem uma cópia do ambiente do pai e não tem access ao ambiente do processo pai. Quando o processo do shell terminar, as alterações feitas no ambiente serão perdidas. Fornecer um arquivo de script é o método mais comumente usado para configurar um ambiente de shell, você pode apenas querer morder o marcador e manter um para cada um dos dois tipos de shell.

    Use a syntax de chamada “script de espaço de ponto”. Por exemplo, aqui está como fazer isso usando o caminho completo para um script:

     . /path/to/set_env_vars.sh 

    E aqui está como fazer isso se você estiver no mesmo diretório do script:

     . set_env_vars.sh 

    Estes executam o script sob o shell atual em vez de carregar outro (o que aconteceria se você fizesse ./set_env_vars.sh ). Como ele é executado no mesmo shell, as variables ​​de ambiente definidas estarão disponíveis quando elas saírem.

    Isso é o mesmo que chamar a source set_env_vars.sh , mas é mais curto para digitar e pode funcionar em alguns lugares onde a source não funciona.

    Você não poderá modificar o shell do chamador porque está em um contexto de processo diferente. Quando os processos filhos herdam as variables ​​do seu shell, eles estão herdando as próprias cópias.

    Uma coisa que você pode fazer é escrever um script que emite os comandos corretos para tcsh ou sh com base em como ele é invocado. Se você é script é “setit” então faça:

     ln -s setit setit-sh 

    e

     ln -s setit setit-csh 

    Agora, diretamente ou em um alias, você faz isso de sh

     eval `setit-sh` 

    ou isso de csh

     eval `setit-csh` 

    setit usa $ 0 para determinar seu estilo de saída.

    Isso é uma reminiscência de como as pessoas usam para obter o conjunto de variables ​​de ambiente TERM.

    A vantagem aqui é que setit é apenas escrito em qualquer shell que você gosta como em:

     #!/bin/bash arg0=$0 arg0=${arg0##*/} for nv in \ NAME1=VALUE1 \ NAME2=VALUE2 do if [ x$arg0 = xsetit-sh ]; then echo 'export '$nv' ;' elif [ x$arg0 = xsetit-csh ]; then echo 'setenv '${nv%%=*}' '${nv##*=}' ;' fi done 

    com os links simbólicos dados acima, e o eval da expressão backquoted, isso tem o resultado desejado.

    Para simplificar a chamada para shells csh, tcsh ou semelhantes:

     alias dosetit 'eval `setit-csh`' 

    ou para sh, bash e similares:

     alias dosetit='eval `setit-sh`' 

    Uma coisa legal sobre isso é que você só precisa manter a lista em um só lugar. Em teoria, você pode até colocar a lista em um arquivo e colocar cat nvpairfilename entre “in” e “do”.

    Isso é bem mais ou menos como as configurações do terminal do shell de login costumavam ser feitas: um script produziria instruções a serem executadas no shell de login. Um alias geralmente seria usado para tornar a chamada simples, como em “tset vt100”. Como mencionado em outra resposta, há também uma funcionalidade semelhante no servidor de notícias INN UseNet.

    No meu .bash_profile eu tenho:

     # No Proxy function noproxy { /usr/local/sbin/noproxy #turn off proxy server unset http_proxy HTTP_PROXY https_proxy HTTPs_PROXY } # Proxy function setproxy { sh /usr/local/sbin/proxyon #turn on proxy server http_proxy=http://127.0.0.1:8118/ HTTP_PROXY=$http_proxy https_proxy=$http_proxy HTTPS_PROXY=$https_proxy export http_proxy https_proxy HTTP_PROXY HTTPS_PROXY } 

    Então, quando eu quero desativar o proxy, as funções são executadas no shell de login e definem as variables ​​como esperado e desejado.

    É “meio que” possível usando gdb e setenv (3) , embora eu tenha dificuldade em recomendar isso. (Adicionalmente, isto é, o ubuntu mais recente não permitirá que você faça isso sem dizer ao kernel para ser mais permissivo sobre o ptrace, e o mesmo pode ser usado para outras distribuições também).

     $ cat setfoo #! /bin/bash gdb /proc/${PPID}/exe ${PPID} </dev/null call setenv("foo", "bar", 0) END $ echo $foo $ ./setfoo $ echo $foo bar 

    Isso funciona – não é o que eu usaria, mas “funciona”. Vamos criar um script teredo para definir a variável de ambiente TEREDO_WORMS :

     #!/bin/ksh export TEREDO_WORMS=ukelele exec $SHELL -i 

    Ele será interpretado pelo shell Korn, exporta a variável de ambiente e, em seguida, substitui-se por um novo shell interativo.

    Antes de executar este script, temos o SHELL configurado no ambiente para o shell C e a variável de ambiente TEREDO_WORMS não está configurada:

     % env | grep SHELL SHELL=/bin/csh % env | grep TEREDO % 

    Quando o script é executado, você está em um novo shell, outro shell C interativo, mas a variável de ambiente está configurada:

     % teredo % env | grep TEREDO TEREDO_WORMS=ukelele % 

    Quando você sai desse shell, o shell original assume:

     % exit % env | grep TEREDO % 

    A variável de ambiente não está definida no ambiente do shell original. Se você usar exec teredo para executar o comando, o shell interativo original será substituído pelo shell Korn que define o ambiente e, em seguida, esse será substituído por um novo shell C interativo:

     % exec teredo % env | grep TEREDO TEREDO_WORMS=ukelele % 

    Se você digitar exit (ou Control-D ), o shell será encerrado, provavelmente efetuando o logout dessa janela ou retornando ao nível anterior de shell de onde os experimentos foram iniciados.

    O mesmo mecanismo funciona para o shell Bash ou Korn. Você pode achar que o prompt após os comandos de saída aparece em lugares engraçados.


    Observe a discussão nos comentários. Esta não é uma solução que eu recomendaria, mas atinge o propósito declarado de um único script para definir o ambiente que funciona com todos os shells (que aceitam a opção -i para fazer um shell interativo). Você também pode adicionar "$@" após a opção de retransmitir quaisquer outros argumentos, o que pode então tornar o shell utilizável como uma ferramenta geral ‘set environment and execute command’. Você pode querer omitir o -i se houver outros argumentos, levando a:

     #!/bin/ksh export TEREDO_WORMS=ukelele exec $SHELL "${@-'-i'}" 

    O "${@-'-i'}" bit significa’ se a lista de argumentos contiver pelo menos um argumento, use a lista de argumentos original; caso contrário, substitua -i pelos argumentos não existentes ‘.

    Você deve usar módulos, veja http://modules.sourceforge.net/

    EDIT: O pacote de módulos não foi atualizado desde 2012, mas ainda funciona bem para o básico. Todos os novos resources, sinos e assobios acontecem em lmod neste dia (que eu gosto mais): https://www.tacc.utexas.edu/research-development/tacc-projects/lmod

    Outra solução alternativa que não vejo mencionada é gravar o valor da variável em um arquivo.

    Eu me deparei com um problema muito semelhante, onde eu queria ser capaz de executar o último teste de conjunto (em vez de todos os meus testes). Meu primeiro plano era escrever um comando para configurar a variável env TESTCASE e, em seguida, ter outro comando que usaria isso para executar o teste. Escusado será dizer que eu tive o mesmo problema exato que você fez.

    Mas então eu criei este hack simples:

    Primeiro comando ( testset ):

     #!/bin/bash if [ $# -eq 1 ] then echo $1 > ~/.TESTCASE echo "TESTCASE has been set to: $1" else echo "Come again?" fi 

    Segundo comando ( testrun ):

     #!/bin/bash TESTCASE=$(cat ~/.TESTCASE) drush test-run $TESTCASE 

    Adicione o sinalizador -l no topo do seu script bash ou seja

     #!/usr/bin/env bash -l ... export NAME1="VALUE1" export NAME2="VALUE2" 

    Os valores com NAME1 e NAME2 agora serão exportados para seu ambiente atual, no entanto, essas alterações não são permanentes. Se você quer que eles sejam permanentes, você precisa adicioná-los ao seu arquivo .bashrc ou outro arquivo init.

    Das man pages:

     -l Make bash act as if it had been invoked as a login shell (see INVOCATION below). 

    Você pode instruir o processo filho para imprimir suas variables ​​de ambiente (chamando “env”), depois fazer um loop sobre as variables ​​de ambiente impressas no processo pai e chamar “export” nessas variables.

    O código a seguir é baseado em Capturing output of find. -print0 em um array bash

    Se o shell pai é o bash, você pode usar

     while IFS= read -r -d $'\0' line; do export "$line" done < <(bash -s <<< 'export VARNAME=something; env -0') echo $VARNAME 

    Se o shell pai for o traço, a read não fornecerá o sinalizador -d e o código ficará mais complicado

     TMPDIR=$(mktemp -d) mkfifo $TMPDIR/fifo (bash -s << "EOF" export VARNAME=something while IFS= read -r -d $'\0' line; do echo $(printf '%q' "$line") done < <(env -0) EOF ) > $TMPDIR/fifo & while read -r line; do export "$(eval echo $line)"; done < $TMPDIR/fifo rm -r $TMPDIR echo $VARNAME 

    Você pode invocar outro Bash com o diferente bash_profile. Além disso, você pode criar um bash_profile especial para uso no ambiente multi-bashprofile.

    Lembre-se de que você pode usar funções dentro do bashprofile e que as funções estarão disponíveis globalmente. por exemplo, “user function {exportar USER_NAME $ 1}” pode definir variables ​​em tempo de execução, por exemplo: user olegchir && env | grep olegchir

    Tecnicamente, isso está correto – somente ‘eval’ não bifurca outro shell. No entanto, do ponto de vista do aplicativo que você está tentando executar no ambiente modificado, a diferença é nula: o filho herda o ambiente de seu pai, portanto, o ambiente (modificado) é transmitido para todos os processos descendentes.

    Ipso facto, a variável de ambiente alterada ‘sticks’ – desde que você esteja executando sob o programa pai / shell.

    Se for absolutamente necessário que a variável de ambiente permaneça após o pai (Perl ou shell) ter saído, é necessário que o shell pai faça o trabalho pesado. Um método que vi na documentação é que o script atual gera um arquivo executável com a linguagem de ‘exportação’ necessária e, em seguida, engana o shell pai para executá-lo – sempre tendo conhecimento do fato de que você precisa iniciar o comando com ‘source’ se você está tentando deixar uma versão não-volátil do ambiente modificado para trás. Um Kluge na melhor das hipóteses.

    O segundo método é modificar o script que inicia o ambiente do shell (.bashrc ou qualquer outro) para conter o parâmetro modificado. Isso pode ser perigoso – se você mantiver o script de boot, ele poderá tornar seu shell indisponível na próxima vez que ele tentar ser iniciado. Existem muitas ferramentas para modificar o shell atual; colocando os ajustes necessários no ‘launcher’, você também efetua o push dessas mudanças. Geralmente não é uma boa ideia; Se você precisar apenas das alterações de ambiente para um conjunto de aplicativos específico, terá que voltar e retornar o script de boot do shell para seu estado original (usando vi ou qualquer outro) posteriormente.

    Em suma, não há methods bons (e fáceis). Presumivelmente, isso foi dificultado para garantir que a segurança do sistema não fosse irrevogavelmente comprometida.

    A resposta curta é não, você não pode alterar o ambiente do processo pai, mas parece que o que você quer é um ambiente com variables ​​de ambiente personalizadas e o shell que o usuário escolheu.

    Então, porque não simplesmente algo como

     #!/usr/bin/env bash FOO=foo $SHELL 

    Então, quando terminar o ambiente, basta exit .

    Você sempre pode usar aliases

     alias your_env='source ~/scripts/your_env.sh' 

    Outra opção é usar “Environment Modules” ( http://modules.sourceforge.net/ ). Infelizmente, isso introduz uma terceira linguagem na mistura. Você define o ambiente com a linguagem do Tcl, mas existem alguns comandos úteis para modificações típicas (prefixados vs. append vs set). Você também precisará ter módulos de ambiente instalados. Você pode usar o module load *XXX* para nomear o ambiente desejado. O comando do módulo é basicamente um apelido para o mecanismo eval descrito acima por Thomas Kammeyer. A principal vantagem aqui é que você pode manter o ambiente em um idioma e confiar em “Módulos de Ambiente” para traduzi-lo para sh, ksh, bash, csh, tcsh, zsh, python (?!? !!), etc.

    Eu fiz isso há muitos anos. Se eu lembro corretamente, incluí um alias em cada .bashrc e .cshrc, com parâmetros, aliasando as respectivas formas de definir o ambiente para um formulário comum.

    Então o script que você irá usar em qualquer uma das duas shells tem um comando com a última forma, que é adequada para cada shell.

    Se eu encontrar os aliases concretos, vou publicá-los.

    Eu criei uma solução usando pipes, eval e signal.

     parent() { if [ -z "$G_EVAL_FD" ]; then die 1 "Rode primeiro parent_setup no processo pai" fi if [ $(ppid) = "$$" ]; then "$@" else kill -SIGUSR1 $$ echo "$@">&$G_EVAL_FD fi } parent_setup() { G_EVAL_FD=99 tempfile=$(mktemp -u) mkfifo "$tempfile" eval "exec $G_EVAL_FD<>'$tempfile'" rm -f "$tempfile" trap "read CMD <&$G_EVAL_FD; eval \"\$CMD\"" USR1 } parent_setup #on parent shell context ( A=1 ); echo $A # prints nothing ( parent A=1 ); echo $A # prints 1 

    Pode funcionar com qualquer comando.

    Sob o bash do OS X, você pode fazer o seguinte:
    Crie o arquivo de script bash para remover a variável

     #!/bin/bash unset http_proxy 

    Torne o arquivo executável

     sudo chmod 744 unsetvar 

    Criar alias

     alias unsetvar='source /your/path/to/the/script/unsetvar' 

    Ele deve estar pronto para ser usado por tanto tempo que você tenha a pasta contendo seu arquivo de script anexado ao caminho.

    Não vejo nenhuma resposta documentando como solucionar esse problema com processos cooperativos. Um padrão comum com coisas como ssh-agent é fazer com que o processo filho imprima uma expressão que o pai possa eval .

     bash$ eval $(shh-agent) 

    Por exemplo, o ssh-agent possui opções para selecionar a syntax de saída compatível com Csh ou Bourne.

     bash$ ssh-agent SSH2_AUTH_SOCK=/tmp/ssh-era/ssh2-10690-agent; export SSH2_AUTH_SOCK; SSH2_AGENT_PID=10691; export SSH2_AGENT_PID; echo Agent pid 10691; 

    (Isso faz com que o agente comece a ser executado, mas não permite que você realmente o use, a menos que você copie e cole essa saída no prompt do shell.) Compare:

     bash$ ssh-agent -c setenv SSH2_AUTH_SOCK /tmp/ssh-era/ssh2-10751-agent; setenv SSH2_AGENT_PID 10752; echo Agent pid 10752; 

    (Como você pode ver, csh e tcsh usam setenv para definir variables.)

    Seu próprio programa pode fazer isso também.

     bash$ foo=$(makefoo) 

    Seu script makefoo simplesmente calcula e imprime o valor, e deixa o chamador fazer o que quiser com ele – atribuí-lo a uma variável é um caso de uso comum, mas provavelmente não é algo que você queira codificar na ferramenta que produz o valor.

    Além de escritas condicionais dependendo do que $ SHELL / $ TERM está definido, não. O que há de errado com o uso do Perl? É bem onipresente (não consigo pensar em uma única variante do UNIX que não tenha), e isso vai poupá-lo do problema.