Salva l'output del comando che modifica l'ambiente in una variabile


8

Come salvare l'output di un comando che modifica l'ambiente in una variabile?

Sto usando la shell bash.

Supponiamo che io abbia:

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

E ora, posso usare i comandi per eseguire f:

$ a=0; b=0; f; echo $a; echo $b; echo $c
Sat Jun 28 21:27:08 CEST 2014: 3 4
3
4

ma vorrei salvare l'output di fin variabile c, quindi ho provato:

a=0; b=0; c=""; c=$(f); echo $a; echo $b; echo $c

ma sfortunatamente, ho:

0
0
Sat Jun 28 21:28:03 CEST 2014: 3 4

quindi non ho alcun cambiamento di ambiente qui.

Come salvare l'output del comando (non solo la funzione) in variabili e salvare le modifiche ambientali?

So che $(...)apre una nuova subshell e questo è il problema, ma è possibile fare qualche soluzione?


Bene, quindi il problema è questo $ae $bsono variabili locali nella tua ffunzione. Potresti exportaverli, ma questo sembra impreciso.
HalosGhost,

1
@HalosGhost: No, non credo. Guarda il primo esempio: si …; f; echo $a; …traduce in 3eco, quindi fmodifica una variabile di shell (e non solo la sua variabile locale).
Scott,

Risposte:


2

Se stai usando Bash 4 o successivo, puoi usare i coprocessi :

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

uscirà

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

coproccrea un nuovo processo eseguendo un determinato comando (qui, cat). Salva il PID COPROC_PIDe i descrittori di file di output / input standard in un array COPROC(proprio come pipe(2), o vedi qui o qui ).

Qui eseguiamo la funzione con l'output standard puntato sul nostro coprocesso in esecuzione cat, e quindi readda esso. Dal momento catche sputa semplicemente il suo input, otteniamo l'output della funzione nella nostra variabile. exec {COPROC[1]}>&-chiude semplicemente il descrittore di file in modo che catnon continui ad aspettare per sempre.


Si noti che readrichiede solo una riga alla volta. È possibile utilizzare mapfileper ottenere una matrice di linee o semplicemente utilizzare il descrittore di file, tuttavia si desidera utilizzarlo in un modo diverso.

exec {COPROC[1]}>&-opere in versioni attuali di Bash, ma le versioni precedenti 4 serie richiedono di salvare il descrittore di file in una variabile semplice prima: fd=${COPROC[1]}; exec {fd}>&-. Se la variabile non è impostata, chiuderà l'output standard.


Se stai usando una versione di 3 serie di Bash, puoi ottenere lo stesso effetto con mkfifo, ma a quel punto non è molto meglio che usare un file reale.


Posso usare solo bash 3.1. I tubi sono difficili da usare. Mktemp ed è l'unica opzione? Non ci sono sintassi o opzioni speciali per eseguire il comando e ottenere l'output ma con modifiche all'ambiente?
Faramir,

È possibile utilizzare mkfifo per creare una pipa denominata come sostituto di coproc, il che non implica in realtà la scrittura dei dati sul disco. Con ciò, reindirizzeresti l'output al fifo e l'input da esso invece del coprocesso. Altrimenti no.
Michael Homer,

+1 per adattare la mia risposta a non utilizzare un file. Ma, nei miei test, il exec {COPROC[1]}>&-comando (almeno a volte) termina immediatamente il coprocesso , quindi il suo output non è più disponibile per la lettura. coproc { cat; sleep 1; }sembra funzionare meglio. (Potrebbe essere necessario aumentare il valore 1se stai leggendo più di una riga.)
Scott

1
C'è un significato convenzionale e ben compreso di "file reale" in uso qui che stai deliberatamente trascurando.
Michael Homer,

1
"File reale" significa un file su disco , come compreso da tutti qui tranne, presumibilmente, tu. Nessuno ha mai sottinteso o permesso a un lettore ragionevole di dedurre che uno di essi era uguale agli argomenti o alle variabili di ambiente. Non mi interessa continuare questa discussione.
Michael Homer,

2

Se stai già contando sul corpo fdell'esecuzione nella stessa shell del chiamante e quindi in grado di modificare variabili come ae b, perché non impostare canche la funzione ? In altre parole:

$ 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

Un possibile motivo potrebbe essere che la variabile di output deve avere nomi diversi (c1, c2, ecc.) Quando viene chiamata in luoghi diversi, ma è possibile risolverlo avendo la funzione impostata c_TEMP e facendo fare ai chiamanti c1=$c_TEMPecc.


1

È un kluge, ma prova

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

(e, facoltativamente, rm c.file).


Grazie. Questa è una risposta semplice e funziona, ma presenta alcuni inconvenienti. So che posso usare mktemp, ma vorrei non creare file aggiuntivi.
Faramir,

1

Basta assegnare tutte le variabili e scrivere l'output contemporaneamente.

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

Ora se lo fai:

f ; echo "$a $b $c"

Il tuo output è:

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

Si noti che questo è un codice portatile completamente POSIX. Inizialmente ho impostato c=la ''stringa nulla perché l'espansione del parametro limita l'assegnazione variabile simultanea + valutazione a solo valori numerici (come $((var=num))) o da valori null o inesistenti - o, in altre parole, non è possibile impostare e valutare contemporaneamente una variabile in una stringa arbitraria se a quella variabile è già stato assegnato un valore. Quindi mi assicuro che sia vuoto prima di provare. Se non avessi svuotato cprima di provare ad assegnarlo, l'espansione restituirebbe solo il vecchio valore.

Giusto per dimostrare:

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

newvalnon è assegnato a $cinline perché oldvalè espanso in ${word}, mentre si verifica sempre l' assegnazione $((aritmetica inline . Ma se ha no ed è vuoto o non impostato ...=))$c oldval

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

... quindi newvalviene subito assegnato e ampliato in $c.

Tutti gli altri modi per farlo comportano una qualche forma di valutazione secondaria. Ad esempio, supponiamo di voler assegnare l'output di f()a una variabile denominata namein un punto e varin un altro. Come attualmente scritto, questo non funzionerà senza impostare il var nell'ambito del chiamante. Un modo diverso potrebbe apparire così:

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"

Ho fornito un esempio meglio formattato di seguito, ma, chiamato come sopra, l'output è:

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

O con differenti $ENVo argomenti:

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

Probabilmente la cosa più difficile da ottenere quando si tratta di valutare due volte è assicurarsi che le variabili non rompano le virgolette ed eseguano codice casuale. Più volte viene valutata una variabile, più diventa difficile. L'espansione dei parametri aiuta molto qui, e l'utilizzo exportal contrario evalè molto più sicuro.

Nell'esempio sopra f()assegna prima $foutla ''stringa nulla e quindi imposta i parametri posizionali per verificare i nomi di variabili validi. Se entrambi i test non vengono superati, viene emesso un messaggio stderre foutviene assegnato il valore predefinito di $fout_name. Indipendentemente dai test, tuttavia, $fout_nameviene sempre assegnato a uno fouto al nome specificato $foute, facoltativamente, al nome specificato viene sempre assegnato il valore di output della funzione. Per dimostrarlo ho scritto questo piccolo forloop:

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

Gioca con alcuni con nomi di variabili ed espansioni di parametri. Se hai una domanda, basta chiedere. Che esegue solo le stesse poche righe nella funzione già rappresentata qui. Vale la pena ricordare almeno che le variabili $ae si $bcomportano diversamente a seconda che siano definite al momento dell'invocazione o impostate già. Tuttavia, fornon fa quasi altro che formattare il set di dati e fornito da f(). Dare un'occhiata:

###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'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.