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'