J'ai essayé de lire l'entrée dans les variables d'environnement à partir de la sortie du programme comme ceci:
echo first second | read A B ; echo $A-$B
Et le résultat est:
-
A et B sont toujours vides. J'ai lu que bash exécutait des commandes piped dans le sous-shell et que, fondamentalement, on empêchait une personne de canaliser l'entrée à lire. Cependant, ce qui suit:
echo first second | while read A B ; do echo $A-$B ; done
Semble fonctionner, le résultat est:
first-second
Quelqu'un peut-il expliquer quelle est la logique ici? Est-ce que les commandes à l'intérieur de la construction while
... done
sont en fait exécutées dans le même shell que echo
et non dans un sous-shell?
Sous bash (et autres Shell également), lorsque vous canalisez quelque chose en utilisant |
à une autre commande, vous créerez implicitement un fork, un sous-shell qui est un enfant de la session en cours et qui ne peut pas affecter l'environnement de la session en cours.
Donc ça:
TOTAL=0
printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 |
while read A B;do
((TOTAL+=A-B))
printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[A-B] $TOTAL
done
echo final total: $TOTAL
ne donnera pas le résultat attendu! :
9 - 4 = 5 -> TOTAL= 5
3 - 1 = 2 -> TOTAL= 7
77 - 2 = 75 -> TOTAL= 82
25 - 12 = 13 -> TOTAL= 95
226 - 664 = -438 -> TOTAL= -343
echo final total: $TOTAL
final total: 0
Où calculé [~ # ~] total [~ # ~] ne pouvait pas être réutilisé dans le script principal.
En utilisant bash Process Substitution, Here Documents ou Here Strings, vous pouvez inverser la fourchette:
read A B <<<"first second"
echo $A
first
echo $B
second
while read A B;do
echo $A-$B
C=$A-$B
done << eodoc
first second
third fourth
eodoc
first-second
third-fourth
en dehors de la boucle:
echo : $C
: third-fourth
TOTAL=0
while read A B;do
((TOTAL+=A-B))
printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[A-B] $TOTAL
done < <(
printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664
)
9 - 4 = 5 -> TOTAL= 5
3 - 1 = 2 -> TOTAL= 7
77 - 2 = 75 -> TOTAL= 82
25 - 12 = 13 -> TOTAL= 95
226 - 664 = -438 -> TOTAL= -343
# and finally out of loop:
echo $TOTAL
-343
Vous pouvez maintenant utiliser $TOTAL
dans votre script principal.
Mais pour travailler uniquement contre stdin, vous pouvez créer une sorte de script dans le fork:
printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 | {
TOTAL=0
while read A B;do
((TOTAL+=A-B))
printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[A-B] $TOTAL
done
echo "Out of the loop total:" $TOTAL
}
Va donner:
9 - 4 = 5 -> TOTAL= 5
3 - 1 = 2 -> TOTAL= 7
77 - 2 = 75 -> TOTAL= 82
25 - 12 = 13 -> TOTAL= 95
226 - 664 = -438 -> TOTAL= -343
Out of the loop total: -343
Remarque: $TOTAL
ne peut pas être utilisé dans script principal (après la dernière parenthèse frisée droite }
).
Comme @CharlesDuffy l'a correctement indiqué, une option bash est utilisée pour modifier ce comportement. Mais pour cela, nous devons d'abord désactiver le contrôle du travail:
shopt -s lastpipe # Set *lastpipe* option
set +m # Disabling job control
TOTAL=0
printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 |
while read A B;do
((TOTAL+=A-B))
printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[A-B] $TOTAL
done
9 - 4 = 5 -> TOTAL= -338
3 - 1 = 2 -> TOTAL= -336
77 - 2 = 75 -> TOTAL= -261
25 - 12 = 13 -> TOTAL= -248
226 - 664 = -438 -> TOTAL= -686
echo final total: $TOTAL
-343
Cela fonctionnera, mais je (personnellement) n'aime pas cela car ce n'est pas standard et n'aidera pas à rendre le script lisible. La désactivation du contrôle des travaux semble également coûteuse pour accéder à ce comportement.
Remarque: Contrôle des travaux est activé par défaut uniquement dans les sessions interactives. Alors set +m
n'est pas requis dans les scripts normaux.
Si oublié set +m
dans un script créerait des comportements différents s'il était exécuté dans une console ou s'il était exécuté dans un script. Cela ne rendra pas cela facile à comprendre ou à déboguer ...
Tout d'abord, cette chaîne de tuyaux est exécutée:
echo first second | read A B
puis
echo $A-$B
Parce que le read A B
est exécuté dans un sous-shell, A et B sont perdus. Si tu fais ça:
echo first second | (read A B ; echo $A-$B)
alors les deux read A B
et echo $A-$B
sont exécutés dans le même sous-shell (voir la page de manuel de bash, recherchez (list)
une solution de rechange beaucoup plus propre ...
read -r a b < <(echo "$first $second")
echo "$a $b"
De cette façon, la lecture n'est pas exécutée dans un sous-shell (ce qui effacerait les variables dès que ce sous-shell serait terminé). Au lieu de cela, les variables que vous souhaitez utiliser sont répercutées dans un sous-shell qui hérite automatiquement des variables du shell parent.
Ce que vous voyez, c'est la séparation entre les processus: le read
se produit dans un sous-shell - un processus séparé qui ne peut pas modifier les variables du processus principal (où les commandes echo
se produiront plus tard).
Un pipeline (comme A | B
) place implicitement chaque composant dans un sous-shell (un processus distinct), même pour les éléments intégrés (comme read
) qui s'exécutent généralement dans le contexte du shell (dans le même processus).
La différence dans le cas de la "canalisation dans" est une illusion. La même règle s'applique ici: la boucle est la seconde moitié d'un pipeline, donc elle est dans une sous-coque, mais la boucle entière est dans la sous-coque même, donc la séparation des processus ne s'applique pas.