Salve a saída do comando que modifica o ambiente em uma variável

Salve a saída do comando que modifica o ambiente em uma variável

Como salvar a saída de um comando que modifica o ambiente em uma variável?

Estou usando o bash shell.

Suponha que eu tenha:

function f () { a=3; b=4 ; echo "`date`: $a $b"; }

E agora, 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 fna 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) em uma variável e salvar as alterações ambientais?

Eu sei que isso $(...)abre um novo subshell e esse é o problema, mas é possível fazer alguma solução alternativa?

Responder1

Se estiver usando o Bash 4 ou posterior, você pode usarcoprocessos:

function f () { a=3; b=4 ; echo "`date`: $a $b"; }
coproc cat
f >&${COPROC[1]}
exec {COPROC[1]}>&-
read c <&${COPROC[0]}
echo a $a
echo b $b
echo c $c

produzirá

a 3
b 4
c Sun Jun 29 10:08:15 NZST 2014: 3 4

coproccria um novo processo executando um determinado comando (aqui, cat). Ele salva o PID COPROC_PIDe os descritores de arquivo de saída/entrada padrão em uma matriz COPROC(assim comopipe(2), ou vejaaquiouaqui).

Aqui executamos a função com a saída padrão apontada para nosso coprocesso em execução cate, em seguida,reada partir dele. Como catapenas 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 que catnão fique esperando para sempre.


Observe que isso readocupa apenas uma linha por vez. Você pode usar mapfilepara obter uma matriz de linhas ou apenas usar o descritor de arquivo da maneira que desejar.

exec {COPROC[1]}>&-funciona nas versões atuais do Bash, mas as versões anteriores da série 4 exigem que você salve primeiro o descritor de arquivo em uma variável simples: 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 de 3 séries do Bash, poderá obter o mesmo efeito commkfifo, mas não é muito melhor do que usar um arquivo real nesse ponto.

Responder2

Se você já está contando com o corpo de fexecução no mesmo shell do chamador, e assim poder modificar variáveis ​​como ae b, por que não fazer com que a função seja definida ctambém? Em outras palavras:

$ function f () { a=3; b=4 ; c=$(echo "$(date): $a $b"); }
$ a=0; b=0; f; echo $a; echo $b; echo $c
3
4
Mon Jun 30 14:32:00 EDT 2014: 3 4

Uma possível razão 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 definindo a função c_TEMP e fazendo com que os chamadores façam c1=$c_TEMPetc.

Responder3

É um kluge, mas tente

f > c.file
c=$(cat c.file)

(e, opcionalmente, rm c.file).

Responder4

Basta atribuir todas as variáveis ​​e escrever a saída ao mesmo tempo.

f() { c= ; echo "${c:=$(date): $((a=3)) $((b=4))}" ; }

Agora, se você fizer isso:

f ; echo "$a $b $c"

Sua saída é:

Tue Jul  1 04:58:17 PDT 2014: 3 4
3 4 Tue Jul  1 04:58:17 PDT 2014: 3 4

Observe que este é um código totalmente portátil POSIX. Eu inicialmente configurei c=para a ''string nula porque a expansão do parâmetro limita a atribuição simultânea de variáveis ​​+ avaliação apenas para números(como $((var=num)))ou de valores nulos ou inexistentes - ou, em outras palavras, você não pode definir simultaneamenteeavaliar uma variável para uma string arbitrária se essa variável já tiver um valor atribuído. Então, apenas garanto que esteja vazio antes de tentar. Se eu não esvaziasse cantes de tentar atribuí-lo, a expansão retornaria apenas o valor antigo.

Apenas para demonstrar:

sh -c '
    c=oldval a=1
    echo ${c:=newval} $((a=a+a))
'
###OUTPUT###
oldval 2

newvalénãoatribuído a $cinline porque oldvalé expandido para ${word}, enquanto a atribuição $((aritmética inline sempre ocorre. Mas se=))$c não tem oldvale está vazio ou não definido...

sh -c '
    c=oldval a=1
    echo ${c:=newval} $((a=a+a)) 
    c= a=$((a+a))
    echo ${c:=newval} $((a=a+a))
'
###OUTPUT###
oldval 2
newval 8

... então newvalé imediatamente atribuído e expandido para $c.

Todas as outras formas de fazer isso envolvem alguma forma de avaliação secundária. Por exemplo, digamos que eu queira atribuir a saída de f()a uma variável nomeada nameem um ponto e varem outro. Conforme escrito atualmente, isso não funcionará sem definir var no escopo do chamador. Uma maneira diferente poderia ser assim:

f(){ fout_name= fout= set -- "${1##[0-9]*}" "${1%%*[![:alnum:]]*}"
    (: ${2:?invalid or unspecified param - name set to fout}) || set --
    export "${fout_name:=${1:-fout}}=${fout:=$(date): $((a=${a:-50}+1)) $((b=${b:-100}-4))}"
    printf %s\\n "$fout"
}

f &&
    printf %s\\n \
        "$fout_name" \
        "$fout" \
        "$a" "$b"

Forneci um exemplo melhor formatado abaixo, mas, chamado como acima, a saída é:

sh: line 2: 2: invalid or unspecified param - name set to fout
Wed Jul  2 02:27:07 PDT 2014: 51 96
fout
Wed Jul  2 02:27:07 PDT 2014: 51 96
51
96

Ou com $ENVargumentos ou diferentes:

b=9 f myvar &&
    printf %s\\n \
        "$fout_name" \
        "$fout" \
        "$myvar" \
        "$a" "$b"

###OUTPUT###

Tue Jul  1 19:56:42 PDT 2014: 52 5
myvar
Tue Jul  1 19:56:42 PDT 2014: 52 5
Tue Jul  1 19:56:42 PDT 2014: 52 5
52
5

Provavelmente, a coisa mais complicada de acertar quando se trata de avaliar duas vezes é garantir que as variáveis ​​não quebrem aspas e executem código aleatório. Quanto mais vezes uma variável é avaliada, mais difícil ela fica. A expansão de parâmetros ajuda muito aqui, e usar exportem vez de evalé muito mais seguro.

No exemplo acima, f()primeiro atribui $fouta ''string nula e, em seguida, define parâmetros posicionais para testar nomes de variáveis ​​válidos. Se ambos os testes não forem aprovados, uma mensagem será emitida stderre o valor padrão foutserá atribuído a $fout_name. Independentemente dos testes, no entanto, o nome que você especifica $fout_nameé sempre atribuído ao nome especificado e , opcionalmente, o nome especificado é sempre atribuído ao valor de saída da função. Para demonstrar isso, escrevi este pequeno loop:fout$foutfor

for v in var '' "wr;\' ong"
    do sleep 10 &&
        a=${a:+$((a*2))} f "$v" || break
    echo "${v:-'' #null}" 
    printf '#\t"$%s" = '"'%s'\n" \
        a "$a" b "$b" \
        fout_name "$fout_name" \
        fout "$fout" \
        '(eval '\''echo "$'\''"$fout_name"\")' \
            "$(eval 'echo "$'"$fout_name"\")"
 done

Ele brinca com nomes de variáveis ​​e expansões de parâmetros. Se tiver alguma questão, basta perguntar. Queapenasexecuta as mesmas poucas linhas na função já representada aqui. Vale a pena mencionar pelo menos que as variáveis $a​​e $bse comportam de maneira diferente dependendo se elas estão definidas na invocação ou já definidas. Ainda assim, ele fornão faz quase nada além de formatar o conjunto de dados fornecido pelo f(). Dar uma olhada:

###OUTPUT###

Wed Jul  2 02:50:17 PDT 2014: 51 96
var
#       "$a" = '51'
#       "$b" = '96'
#       "$fout_name" = 'var'
#       "$fout" = 'Wed Jul  2 02:50:17 PDT 2014: 51 96'
#       "$(eval 'echo "$'"$fout_name"\")" = 'Wed Jul  2 02:50:17 PDT 2014: 51 96'
sh: line 2: 2: invalid or unspecified param - name set to fout
Wed Jul  2 02:50:27 PDT 2014: 103 92
'' #null
#       "$a" = '103'
#       "$b" = '92'
#       "$fout_name" = 'fout'
#       "$fout" = 'Wed Jul  2 02:50:27 PDT 2014: 103 92'
#       "$(eval 'echo "$'"$fout_name"\")" = 'Wed Jul  2 02:50:27 PDT 2014: 103 92'
sh: line 2: 2: invalid or unspecified param - name set to fout
Wed Jul  2 02:50:37 PDT 2014: 207 88
wr;\' ong
#       "$a" = '207'
#       "$b" = '88'
#       "$fout_name" = 'fout'
#       "$fout" = 'Wed Jul  2 02:50:37 PDT 2014: 207 88'
#       "$(eval 'echo "$'"$fout_name"\")" = 'Wed Jul  2 02:50:37 PDT 2014: 207 88'

informação relacionada