en bash, lu après qu'un tuyau ne définit pas de valeurs

22

Edit: le titre d'origine était "la lecture échoue dans bash"

Avec ksh, j'utilise read comme moyen pratique de séparer les valeurs:

$ echo 1 2 3 4 5 | read a b dump
$ echo $b $a 
2 1
$

Mais cela échoue en bash:

$ echo 1 2 3 4 5 | read a b dump
$ echo $b $a 

$

Je n'ai trouvé aucune raison dans la page de manuel pour laquelle il échoue, une idée?

Emmanuel
la source
2
Ceci est discuté (quelque peu obscurément) à la page 024 de la FAQ de Greg's Bash .
Scott

Réponses:

27

bash exécute le côté droit d'un pipeline dans un contexte de sous - shell , donc les modifications apportées aux variables (ce qui est le readcas) ne sont pas conservées - elles meurent lorsque le sous-shell le fait, à la fin de la commande.

Au lieu de cela, vous pouvez utiliser la substitution de processus :

$ read a b dump < <(echo 1 2 3 4 5)
$ echo $b $a
2 1

Dans ce cas, reads'exécute dans notre shell principal et notre commande de production de sortie s'exécute dans le sous-shell. La <(...)syntaxe crée un sous-shell et connecte sa sortie à un tube, que nous redirigeons en entrée de readavec l' <opération ordinaire . Parce que readexécuté dans notre shell principal, les variables sont définies correctement.

Comme indiqué dans un commentaire, si votre objectif est littéralement de diviser une chaîne en variables, vous pouvez utiliser une chaîne ici :

read a b dump <<<"1 2 3 4 5"

Je suppose qu'il y a plus que cela, mais c'est une meilleure option s'il n'y en a pas.

Michael Homer
la source
3
Ou même read a b dump <<< '1 2 3 4 5'.
choroba
Merci à tous, btw j'ai remarqué que mksh (sur cygwin) fait la même chose que bash.
Emmanuel
@Michael Homer Bonne explication! J'ai trouvé un autre exemple qui peut expliquer que toutes les commandes de pipeline exécuté en propre sous - shell: cat /etc/passwd | (read -r line ; echo $line). Mais à côté echode ce $linequi est pas rien mis de pipeline à l' écran, parce que la valeur a été existait juste entre parenthèses (sous - shell). J'espère que ça aide quelqu'un.
Yurij Goncharuk
17

Ce n'est pas un bashbug car POSIXpermet à la fois bashet les kshcomportements, conduisant à la fâcheuse différence que vous observez.

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_12

De plus, chaque commande d'un pipeline multi-commandes se trouve dans un environnement de sous-shell; cependant, en tant qu'extension, une ou toutes les commandes d'un pipeline peuvent être exécutées dans l'environnement actuel. Toutes les autres commandes doivent être exécutées dans l'environnement shell actuel.

Cependant, avec bash 4.2et plus récent, vous pouvez définir l' lastpipeoption dans des scripts non interactifs pour obtenir le résultat attendu, par exemple:

#!/bin/bash

echo 1 2 3 4 5 | read a b dump
echo before: $b $a 
shopt -s lastpipe
echo 1 2 3 4 5 | read a b dump
echo after: $b $a 

Production:

before:
after: 2 1
jlliagre
la source
1
+1 merci pour l'info "lastpipe". désolé pour le retard
Emmanuel
1
le problème lastpipeest qu'il ne fonctionne pas dans d'autres coques (par exemple, tiret). il n'y a fondamentalement aucun moyen de faire ce portable à court de tout exécuter dans ce sous-shell, voir stackoverflow.com/questions/36268479/…
anarcat
1
@anarcat C'est correct mais la question posée ici concernait bash.
jlliagre
@anarcat: Cela peut changer à l'avenir car il y a un souhait de changer POSIX pour une autre raison (statut PIPE) voir ici: unix.stackexchange.com/questions/476834/… Changer d'autres shells n'est pas anodin, cela m'a pris plusieurs mois pour réécrire l'analyseur et l'interpètre sur le Bourne Shell (bosh) pour implémenter le comportement plus rapide du ksh moderne.
schily