Enregistrer la sortie de la command qui modifie l'environnement dans une variable

Comment sauvegarder la sortie d'une command qui modifie l'environnement en une variable?

J'utilise shell bash.

Supposons que j'ai:

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

Et maintenant, je peux utiliser des commands pour exécuter f :

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

mais je voudrais sauver la sortie de f à la variable c , alors j'ai essayé:

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

mais malheureusement, j'ai:

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

donc je n'ai pas de changement d'environnement ici.

Comment sauvegarder la sortie de la command (pas seulement fonction) à la variable et save les changements environnementaux?

Je sais que $(...) ouvre un nouveau sous-shell et c'est le problème, mais est-il possible de faire quelque chose?

Si vous utilisez Bash 4 ou une version ultérieure, vous pouvez utiliser coprocesses :

 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 

sortira

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

coproc crée un nouveau process exécutant une command donnée (ici, cat ). Il enregistre le PID dans COPROC_PID et les descripteurs de file de sortie / input standard dans un tableau COPROC (tout comme pipe(2) ou voir ici ou ici ).

Ici, nous exécutons la fonction avec une sortie standard pointée sur notre coprocess running cat , puis read -la. Puisque cat crache juste ses inputs, nous obtenons la sortie de la fonction dans notre variable. exec {COPROC[1]}>&- ferme simplement le descripteur de file afin que le cat ne rest pas en attente pour toujours.


Notez que read ne prend qu'une ligne à la fois. Vous pouvez utiliser mapfile pour get un tableau de lignes, ou simplement utiliser le descripteur de file, mais vous souhaitez l'utiliser différemment.

exec {COPROC[1]}>&- fonctionne dans les versions actuelles de Bash, mais les versions précédentes de la série 4 vous requestnt d'save le descripteur de file en une variable simple: fd=${COPROC[1]}; exec {fd}>&- fd=${COPROC[1]}; exec {fd}>&- . Si votre variable est désactivée, elle ferme la sortie standard.


Si vous utilisez une version 3 de Bash, vous pouvez get le même effet avec mkfifo , mais ce n'est pas beaucoup mieux que d'utiliser un file réel à ce moment-là.

Si vous countz déjà sur le corps de f s'exécute dans le même shell que l'appelant et que vous pouvez ainsi modifier des variables comme a et b , pourquoi ne pas simplement définir la fonction c ? En d'autres termes:

 $ 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 

Une raison possible peut être que la variable de sortie doit avoir des noms différents (c1, c2, etc) quand elle est appelée à différents endroits, mais vous pouvez répondre à cette question en utilisant la fonction c_TEMP et en appelant c1=$c_TEMP etc.

C'est un kluge, mais essayez

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

(et, facultativement, rm c.file ).

Il suffit d'assigner toutes les variables et d'écrire la sortie en même time.

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

Maintenant si vous le faites:

 f ; echo "$a $b $c" 

Votre sortie est:

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

Notez que ceci est entièrement le code portable POSIX. Initialement, je place c= à la string '' null '' car l'expansion des parameters limite l'assignation des variables simultanées + l'évaluation soit uniquement numérique (comme $((var=num)) ) ou des valeurs nulles ou inexistantes 't simultanément définir et évaluer une variable à une string arbitraire si cette variable est déjà assignée une valeur. Donc, je m'assure juste qu'il est vide avant d'essayer. Si je n'ai pas c vide c avant de tenter de l'assigner, l'extension ne returnnerait que l'ancienne valeur.

Juste pour démontrer:

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

newval n'est pas assigné à $c inline car oldval est étendu à ${word} , alors que $(( inline $(( arithmetic = assignment )) se produit toujours. Mais si $c n'a pas oldval et est vide ou non …

 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 

… alors newval est à la fois affecté et étendu dans $c .

Toutes les autres façons de le faire impliquent une forme d'évaluation secondaire. Par exemple, disons que je souhaitais affecter la sortie de f() à une variable nommée name à un point et var à une autre. Comme cela est écrit actuellement, cela ne fonctionnera pas sans définir le var dans la scope de l'appelant. Une façon différente pourrait ressembler à ceci, cependant:

 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" 

J'ai fourni un meilleur exemple formaté ci-dessous, mais, appelé comme ci-dessus la sortie est:

 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 avec différents $ENV ou arguments:

 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 

Probablement la chose la plus délicate à faire quand il s'agit d'évaluer deux fois est de s'assurer que les variables ne cassent pas les guillemets et exécutent du code random. Plus une variable est évaluée plus elle est difficile à get. L'expansion des parameters aide énormément ici, et l'utilisation de l' export par opposition à eval est beaucoup plus sûre.

Dans l'exemple ci-dessus, f() affecte d'abord $fout la string '' null '' , puis définit les parameters positionnels pour tester les noms de variables valides. Si les deux tests ne réussissent pas, un message est émis dans stderr et la valeur par défaut de fout est assignée à $fout_name . Indépendamment des tests, $fout_name est toujours atsortingbué à fout ou au nom que vous spécifiez et $fout et, optionnellement, votre nom spécifié est toujours affecté à la valeur de sortie de la fonction. Pour démontrer cela, j'ai écrit cette petite boucle:

 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 

Il joue autour de certains avec des noms de variables et des expansions de parameters. Si vous avez une question, il suffit de requestr. Cela n'exécute que les mêmes lignes dans la fonction déjà représentée ici. Il convient de mentionner au less que les variables $a et $b se comportent différemment selon qu'elles sont définies lors de l'invocation ou déjà définies. Pourtant, le for ne fait presque rien mais formater l'set de données et fourni par f() . Regarde:

 ###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'