Pourquoi dois-je mettre la commande read dans un sous-shell tout en utilisant pipeline [duplicate]

Nov 30 2020

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|yredirigera la sortie de xvers l'entrée de y. Pourquoi read a; read a b; echo $ane peut pas obtenir l'entrée mais un sous-shell le peut?

Réponses

8 muru Dec 01 2020 at 03:10

Le principal problème ici est de regrouper correctement les commandes. Les sous-coquilles sont un problème secondaire.

x|yredirigera la sortie de xvers l'entrée dey

Oui, mais x | y; zne redirigera pas la sortie de xvers les deux yet z.

Dans df . | read a; read a b; echo "$a", le pipeline se connecte uniquement df .et read ales autres commandes n'ont aucune connexion avec ce pipeline. Vous devez regrouper les reads 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 echocommande doit donc être dans le même sous-shell que le reads. 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.

3 glennjackman Dec 01 2020 at 03:10

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}' )
2 StephenHarris Dec 01 2020 at 03:11

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

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 bashet le côté droit d'un pipeline qui exécute un sous-shell, donc les $a $bvaleurs ne sont pas accessibles en dehors de la whileboucle, mais c'est un problème différent!