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

8

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, 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 gostaria de salvar a saída de f na 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 eu não tenho nenhuma mudança de ambiente aqui.

Como salvar a saída do comando (não apenas a função) para a variável e salvar as alterações ambientais?

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

    
por faramir 28.06.2014 / 21:32

4 respostas

2

Se você estiver usando o Bash 4 ou posterior, poderá usar coprocessos :

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

coproc cria um novo processo executando um determinado comando (aqui, cat ). Ele salva o PID em COPROC_PID e os descritores de arquivo de saída / entrada padrão em uma matriz COPROC (exatamente como pipe(2) , ou veja aqui ou aqui ).

Aqui executamos a função com a saída padrão apontada para o nosso coprocess executando cat , e então read dele. Como cat apenas retorna sua entrada, obtemos a saída da função em nossa variável. exec {COPROC[1]}>&- apenas fecha o descritor de arquivo para que cat não fique esperando para sempre.

Observe que read usa apenas uma linha por vez. Você pode usar mapfile para obter uma matriz de linhas ou apenas usar o descritor de arquivo, mas você quer usá-lo de uma maneira diferente.

exec {COPROC[1]}>&- funciona nas versões atuais do Bash, mas as versões anteriores de quatro séries exigem que você salve o descritor de arquivo em uma variável simples primeiro: fd=${COPROC[1]}; exec {fd}>&- . Se a sua variável não estiver definida, fechará a saída padrão.

Se você estiver usando uma versão de 3 séries do Bash, você pode obter o mesmo efeito com mkfifo , mas não é muito melhor do que usar um arquivo real nesse ponto.

    
por 29.06.2014 / 00:31
2

Se você já está contando com o corpo de f executando no mesmo shell que o chamador e, portanto, podendo modificar variáveis como a e b , por que não fazer a função apenas definir c també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

Um possível motivo pode ser que a variável de saída precise ter nomes diferentes (c1, c2, etc) quando chamados em locais diferentes, mas você poderia endereçar isso definindo a função c_TEMP e fazendo com que os chamadores fizessem c1=$c_TEMP etc .

    
por 30.06.2014 / 20:33
1

É um kluge, mas tente

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

(e, opcionalmente, rm c.file ).

    
por 28.06.2014 / 22:42
1

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 isso é totalmente código portátil POSIX. Inicialmente, defini c= para a string '' null porque a expansão do parâmetro limita a atribuição de variáveis simultâneas + avaliação a somente numéricos (como $((var=num)) ) ou de valores nulos ou inexistentes - ou, em outros palavras, você não pode definir simultaneamente e avaliar uma variável para uma string arbitrária se essa variável já tiver um valor atribuído. Então eu só garanto que está vazio antes de tentar. Se eu não esvaziar c antes de tentar atribuí-lo, a expansão só retornaria o valor antigo.

Apenas para demonstrar:

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

newval é não atribuído a $c em linha porque oldval é expandido em ${word} , enquanto a% em linha$(( aritmética = designação )) sempre ocorre. Mas se $c não tiver oldval e estiver vazio ou não estiver 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 é atribuído e expandido em $c .

Todas as outras formas de fazer isso envolvem alguma forma de avaliação secundária. Por exemplo, digamos que eu quisesse atribuir a saída de f() a uma variável chamada name em um ponto e var em outro. Como atualmente escrito, isso não funcionará sem definir o 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"

Eu forneci um exemplo formatado melhor 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 diferentes $ENV ou argumentos:

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 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 muito aqui e usar export em oposição a eval é muito mais seguro.

No exemplo acima, f() primeiro atribui $fout a '' string nula e, em seguida, configura os parâmetros posicionais para testar os nomes de variáveis válidos. Se ambos os testes não forem transmitidos, será emitida uma mensagem para stderr e o valor padrão de fout será atribuído a $fout_name . Independentemente dos testes, no entanto, $fout_name é sempre atribuído a fout ou o nome especificado e $fout e, opcionalmente, o nome especificado sempre recebe o valor de saída da função. Para demonstrar isso eu escrevi este pequeno for loop:

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 toca em alguns com nomes de variáveis e expansões de parâmetros. Se tiver alguma questão, basta perguntar. Que somente executa as mesmas poucas linhas na função já representada aqui. Vale a pena mencionar, pelo menos, que as variáveis $a e $b se comportam de maneira diferente, dependendo de serem definidas na invocação ou definidas já. Ainda assim, o for não faz quase nada, mas formata o conjunto de dados e é fornecido por f() . Dê 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'
    
por 01.07.2014 / 13:04