Pourquoi dois-je mettre la commande read dans un sous-shell tout en utilisant pipeline [duplicate]
La commande df .
peut nous montrer sur quel appareil nous sommes. Par exemple,
me@ubuntu1804:~$ df .
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sdb1 61664044 8510340 49991644 15% /home
Maintenant, je veux obtenir la chaîne /dev/sdb1
.
J'ai essayé comme ça mais ça n'a pas fonctionné:, df . | read a; read a b; echo "$a"
cette commande m'a donné une sortie vide. Mais df . | (read a; read a b; echo "$a")
fonctionnera comme prévu.
Je suis un peu confus maintenant.
Je sais que (read a; read a b; echo "$a")
c'est un sous-shell, mais je ne sais pas pourquoi je dois créer un sous-shell ici. Selon ma compréhension, x|y
redirigera la sortie de x
vers l'entrée de y
. Pourquoi read a; read a b; echo $a
ne peut pas obtenir l'entrée mais un sous-shell le peut?
Réponses
Le principal problème ici est de regrouper correctement les commandes. Les sous-coquilles sont un problème secondaire.
x|y
redirigera la sortie dex
vers l'entrée dey
Oui, mais x | y; z
ne redirigera pas la sortie de x
vers les deux y
et z
.
Dans df . | read a; read a b; echo "$a"
, le pipeline se connecte uniquement df .
et read a
les autres commandes n'ont aucune connexion avec ce pipeline. Vous devez regrouper les read
s ensemble: df . | { read a; read a b; }
ou df . | (read a; read a b)
pour que le pipeline soit connecté aux deux.
Cependant, vient maintenant le problème du sous-shell: les commandes d'un pipeline sont exécutées dans un sous-shell, donc la définition d'une variable dans celles-ci n'affecte pas le shell parent. La echo
commande doit donc être dans le même sous-shell que le read
s. Donc: df . | { read a; read a b; echo "$a"; }
.
Maintenant, que vous utilisiez ( ... )
ou { ...; }
ne fasse aucune différence particulière ici puisque les commandes d'un pipeline sont exécutées de toute façon dans des sous-shell.
Une alternative consiste à utiliser une sous- station de processus :
{ read header; read filesystem rest; } < <(df .)
echo "$filesystem"
La <(...)
substitution de processus exécute le script contenu (dans un sous-shell), mais il agit comme un nom de fichier, vous avez donc besoin du premier <
pour rediriger le contenu (qui est la sortie du script) dans le script accolé. Les commandes groupées sont exécutées dans le shel courant ;.
Il peut être difficile de rendre cela lisible, mais vous pouvez mettre n'importe quel espace arbitraire entre les accolades et la substitution de processus.
{
read header
read filesystem rest
} < <(
df .
)
echo "$filesystem"
Et il peut être plus facile d'utiliser un outil externe pour extraire le système de fichiers:
filesystem=$( df . | awk 'NR == 2 {print $1}' )
Votre première commande
df . | read a; read a b; echo "$a"
est effectivement interprété comme
( df . | read a ) ; read a b; echo "$a"
Ainsi, le pipeline alimente uniquement la read a
commande.
Puisque vous souhaitez plusieurs lectures à partir du pipeline, vous devez regrouper les commandes.
Maintenant, il n'est pas nécessaire que ce soit un sous-shell; ça pourrait être un groupement.
bash-4.2$ df | { read a ; read a b ; echo $a ; }
devtmpfs
Plus généralement, vous voudrez peut-être une boucle
bash-4.2$ df | while read a > do > read a b > echo $a
> done
devtmpfs
tmpfs
/dev/vda3
/dev/vdb
Il y a un problème secondaire avec bash
et le côté droit d'un pipeline qui exécute un sous-shell, donc les $a
$b
valeurs ne sont pas accessibles en dehors de la while
boucle, mais c'est un problème différent!