Como salvar a saída de um comando que modifica o ambiente em uma variável?
Estou usando o shell bash.
Suponha que eu tenha:
function f () { a=3; b=4 ; echo "`date`: $a $b"; }
E agora, eu posso usar comandos para executar f
:
$ a=0; b=0; f; echo $a; echo $b; echo $c
Sat Jun 28 21:27:08 CEST 2014: 3 4
3
4
mas eu gostaria de salvar a saída da f
variável c
, então tentei:
a=0; b=0; c=""; c=$(f); echo $a; echo $b; echo $c
mas infelizmente, eu tenho:
0
0
Sat Jun 28 21:28:03 CEST 2014: 3 4
então não tenho nenhuma mudança de ambiente aqui.
Como salvar a saída do comando (não apenas a função) na variável e salvar as mudanças ambientais?
Eu sei que isso $(...)
abre um novo subshell e esse é o problema, mas é possível fazer alguma solução alternativa?
bash
environment-variables
output
faramir
fonte
fonte
$a
e$b
são variáveis locais em suaf
função. Você poderiaexport
, mas isso parece incompleto.…; f; echo $a; …
resulta em3
eco, assimf
como modificar uma variável de shell (e não apenas sua própria variável local).Respostas:
Se você estiver usando o Bash 4 ou posterior, poderá usar coprocesses :
irá produzir
coproc
cria um novo processo executando um determinado comando (aqui,cat
). Ele salva o PIDCOPROC_PID
e os descritores de arquivo de entrada / saída padrão em uma matrizCOPROC
(assim comopipe(2)
, ou veja aqui ou aqui ).Aqui, executamos a função com saída padrão apontada para o nosso co-processo em execução
cat
e depois aread
partir dele. Comocat
apenas cospe sua entrada de volta, obtemos a saída da função em nossa variável.exec {COPROC[1]}>&-
apenas fecha o descritor de arquivo para quecat
não fique esperando para sempre.Observe que
read
leva apenas uma linha por vez. Você pode usarmapfile
para obter uma matriz de linhas ou apenas usar o descritor de arquivo da maneira que desejar.exec {COPROC[1]}>&-
trabalhos em versões atuais do Bash, mas versões anteriores da série 4 exigem que você salvar o descritor de arquivo em uma variável simples em primeiro lugar:fd=${COPROC[1]}; exec {fd}>&-
. Se sua variável não estiver definida, ela fechará a saída padrão.Se você estiver usando uma versão em série do Bash, poderá obter o mesmo efeito
mkfifo
, mas não é muito melhor do que usar um arquivo real nesse momento.fonte
exec {COPROC[1]}>&-
comando (pelo menos algumas vezes) termina o coprocesso imediatamente , portanto sua saída não está mais disponível para ser lida.coproc { cat; sleep 1; }
parece funcionar melhor. (Você pode precisar aumentar o1
se você está lendo mais de uma linha.)Se você já está contando com o corpo de
f
executar no mesmo shell que o responsável pela chamada e, portanto, pode modificar variáveis comoa
eb
, por que não definir a funçãoc
também? Em outras palavras:Uma razão possível pode ser que a variável de saída precise ter nomes diferentes (c1, c2, etc) quando chamada em locais diferentes, mas você pode resolver isso com a função definida c_TEMP e com os chamadores,
c1=$c_TEMP
etc.fonte
É um kluge, mas tente
(e, opcionalmente
rm c.file
).fonte
mktemp
, mas gostaria de não criar arquivos adicionais.Apenas atribua todas as variáveis e escreva a saída ao mesmo tempo.
Agora, se você fizer:
Sua saída é:
Observe que este é um código portátil totalmente POSIX. Inicialmente, defino
c=
a''
cadeia nula porque a expansão do parâmetro limita a atribuição simultânea de variáveis + a avaliação a valores numéricos (como$((var=num))
) ou nulos ou inexistentes - ou, em outras palavras, você não pode configurar e avaliar simultaneamente uma variável em uma cadeia arbitrária se essa variável já tiver um valor atribuído. Portanto, apenas garanto que ele esteja vazio antes de tentar. Se eu não esvaziassec
antes de tentar atribuí-lo, a expansão retornaria apenas o valor antigo.Apenas para demonstrar:
newval
não é atribuído ao$c
inline porqueoldval
é expandido para${word}
, enquanto a atribuição$((
aritmética inline sempre ocorre. Mas se não tem e está vazio ou não está definido ...=
))
$c
oldval
... então
newval
é imediatamente atribuído e expandido para$c
.Todas as outras maneiras de fazer isso envolvem alguma forma de avaliação secundária. Por exemplo, digamos que eu desejasse atribuir a saída de
f()
a uma variável nomeadaname
em um ponto evar
em outro. Como está escrito atualmente, isso não funcionará sem definir o var no escopo do chamador. Uma maneira diferente poderia ser assim:Forneci um exemplo melhor formatado abaixo, mas chamado como acima, a saída é:
Ou com diferentes
$ENV
ou argumentos:Provavelmente, a coisa mais difícil de acertar quando se trata de avaliar duas vezes é garantir que as variáveis não quebrem aspas e executem códigos aleatórios. Quanto mais vezes uma variável é avaliada, mais difícil fica. A expansão de parâmetros ajuda bastante aqui, e usar
export
ao contrárioeval
é muito mais seguro.No exemplo acima,
f()
primeiro atribui$fout
a''
cadeia nula e, em seguida, define parâmetros posicionais para testar nomes de variáveis válidos. Se ambos os testes não passarem, uma mensagem será emitidastderr
e o valor padrão defout
será atribuído$fout_name
. Independentemente dos testes, no entanto,$fout_name
sempre é atribuído a umfout
ou ao nome que você especificar$fout
e, opcionalmente, seu nome especificado sempre recebe o valor de saída da função. Para demonstrar isso, escrevi este pequenofor
loop:Ele brinca com alguns com nomes de variáveis e expansões de parâmetros. Se tiver alguma questão, basta perguntar. Isso executa apenas as mesmas poucas linhas na função já representada aqui. Vale ressaltar, pelo menos, que as variáveis
$a
e$b
se comportam de maneira diferente, dependendo se elas estão definidas na chamada ou já estão definidas. Ainda assim,for
ele quase não formata o conjunto de dados e é fornecido porf()
. Dar uma olhada:fonte