Pourquoi l’entrée de la tuyauterie pour «lire» ne fonctionne que lorsqu’elle est introduite dans la construction «while read…»?

J’ai essayé de lire les entrées dans les variables d’environnement à partir de la sortie du programme comme ceci:

echo first second | read AB ; echo $A-$B 

Et le résultat est:

 - 

A et B sont toujours vides. J’ai lu à propos de bash exécutant des commandes de canalisation dans un sous-shell et cela empêchait fondamentalement un de l’entrée de canalisation à lire. Cependant, les éléments suivants:

 echo first second | while read AB ; do echo $A-$B ; done 

Semble fonctionner, le résultat est:

 first-second 

Quelqu’un peut-il s’il vous plaît expliquer quelle est la logique ici? Est-ce que les commandes contenues dans la construction whiledone sont réellement exécutées dans le même shell que echo et non dans un sous-shell?

Comment faire une boucle contre stdin et obtenir le résultat stocké dans une variable

Sous bash (et autre shell également), lorsque vous utilisez 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 AB;do ((TOTAL+=AB)) printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[AB] $TOTAL done echo final total: $TOTAL 

ne donnera pas le résultat escompté! :

  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 

TOTAL calculé ne pouvait pas être réutilisé dans le script principal.

Inverser la fourche

En utilisant bash Process Substitution , Here Documents ou Here Ssortingngs , vous pouvez inverser le fork:

Ici des cordes

 read AB < <<"first second" echo $A first echo $B second 

Ici des documents

 while read AB;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 

Ici les commandes

 TOTAL=0 while read AB;do ((TOTAL+=AB)) printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[AB] $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 .

Piping vers une liste de commandes

Mais pour travailler uniquement avec stdin , vous pouvez créer une sorte de script dans fork :

 printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 | { TOTAL=0 while read AB;do ((TOTAL+=AB)) printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[AB] $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 n'a pas pu être utilisé dans le script principal (après le dernier crochet } ).

Utilisation de l’option lastpipe bash

Comme @CharlesDuffy l'a souligné correctement, une option bash est utilisée pour modifier ce comportement. Mais pour cela, nous devons d'abord désactiver le contrôle des travaux :

 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 AB;do ((TOTAL+=AB)) printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[AB] $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 (personnellement) je n'aime pas cela car ce n'est pas standard et ne consortingbuera pas à rendre le script lisible. Désactiver également le contrôle du travail semble coûteux pour accéder à ce comportement.

Remarque: Le contrôle des travaux est activé par défaut uniquement dans les sessions interactives . Donc, set +m n'est pas requirejs dans les scripts normaux.

Si vous avez oublié set +m dans un script, cela créerait des comportements différents si vous les exécutiez dans une console ou si vous les exécutiez dans un script. Cela ne va pas rendre cela facile à comprendre ou à déboguer ...

Tout d’abord, cette chaîne est exécutée:

 echo first second | read AB 

puis

 echo $A-$B 

Parce que la read AB est exécutée dans un sous-shell, A et B sont perdus. Si tu fais ça:

 echo first second | (read AB ; echo $A-$B) 

alors les deux read AB et echo $A-$B sont exécutés dans le même sous-shell (voir la page de manuel de bash, recherchez (list)

un travail beaucoup plus propre …

 read -rab < <(echo "$first $second") echo "$a $b" 

De cette façon, read n'est pas exécuté dans un sous-shell (ce qui effacerait les variables dès que ce sous-shell est 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 est la séparation entre les processus: la read se produit dans un sous-shell – un processus distinct qui ne peut pas modifier les variables du processus principal (où echo commandes d’ 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 composants 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 du “piping into while” est une illusion. La même règle s’applique ici: la boucle est la seconde moitié d’un pipeline, elle se trouve donc dans un sous-shell, mais la boucle entière se trouve dans le même sous-shell, de sorte que la séparation des processus ne s’applique pas.