web-dev-qa-db-fra.com

Pourquoi l'entrée de tuyauterie pour "lire" ne fonctionne que lorsqu'elle est introduite dans la construction "pendant la lecture ..."?

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?

69
huoneusto

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

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.

Inverser la fourche

En utilisant bash Process Substitution, Here Documents ou Here Strings, vous pouvez inverser la fourchette:

Ici les cordes

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

echo $B
second

Ici Documents

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

Commandes ici

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.

Tuyauterie vers une liste de commandes

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 }).

Utilisation de lastpipe option bash

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 ...

63
F. Hauri

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)

21
pbhd

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.

16
immotus

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.

1
Martin Kealey