Quelle est la différence entre && et | dans le script bash?

13

Prenons les deux lignes ci-dessous qui nous donnent deux résultats différents.

p=$(cd ~ && pwd) ; echo $p
p=$(cd ~ |  pwd) ; echo $p

Comment les deux diffèrent-ils?

Nam G VU
la source
@heemayl Merci. Pouvez-vous extraire les différences sur mon cas? Merci.
Nam G VU
1
Astuce: Les commandes de chaque côté de l' |exécution en sous-coquilles.
heemayl

Réponses:

21

Dans p=$(cd ~ && pwd):

  • La substitution de commande $(), s'exécute en sous-shell

  • cd ~change le répertoire en ~(votre home), si cdréussit ( &&) puis pwdimprime le nom du répertoire sur STDOUT, donc la chaîne sauvegardée psera votre répertoire home par exemple/home/foobar

Dans p=$(cd ~ | pwd):

  • $()Génère à nouveau un sous-shell

  • Les commandes des deux côtés de la |course dans les sous-coquilles respectives (et les deux démarrent en même temps)

  • ainsi cd ~se fait dans un sous-shell, et pwddans un sous-shell séparé

  • donc vous obtiendrez seulement le STDOUT à partir pwdd'où vous exécutez la commande, cela peut être n'importe quel répertoire comme vous pouvez l'imaginer, donc pcontiendra le nom du répertoire d'où la commande est invoquée, pas votre répertoire personnel

heemayl
la source
À quoi sert le canal entre les commandes? cd ~ne produit aucune sortie et pwdne lit aucune entrée.
Barmar
La deuxième commande est essentiellement équivalente à (cd ~);p=$(pwd)non?
Barmar
@Barmar Oui, c'est ce qu'OP a utilisé, et je l'explique juste pour lui.
heemayl
7

Le problème principal est de savoir comment les opérateurs &&et |connecter les deux commandes.

Le &&connecte les commandes via le code de sortie. Le |connecte les deux commandes via les descripteurs de fichiers (stdin, stdout).

Simplifions d'abord. Nous pouvons supprimer l'affectation et écrire:

echo $(cd ~ && pwd)
echo $(cd ~ | pwd)

Nous pouvons même supprimer le sous-shell d'exécution de commande pour analyser ceci:

$ cd ~ && pwd
$ cd ~ | pwd

&&

Si nous changeons l'invite pour afficher le répertoire où les commandes sont exécutées, quelque chose comme PS1='\w\$ ', nous verrons ceci:

/tmp/user$ cd ~ && pwd
/home/user
~$ 
  • La commande a cd ~changé le "répertoire actuel" au domicile de l'utilisateur réel qui exécute la commande ( /home/user).
  • Comme le résultat de la commande a réussi (code de sortie 0), la commande suivante après l'exécution du &&
  • Et le "répertoire de travail actuel" est imprimé.
  • La coque en cours d' exécution a changé pwdà ~comme le montre l'invite de ~$.

Si le changement de répertoire a échoué (code de sortie non 0) pour une raison quelconque (le répertoire n'existe pas, les autorisations bloquent la lecture du répertoire), la commande suivante ne sera pas exécutée.

Exemple:

/tmp/user$ false && pwd
/tmp/user$ _ 

Le code de sortie de 1 falseempêche l'exécution de la commande suivante.

Ainsi, le code de sortie de la "commande 1" est ce qui affecte la "commande 2".

Maintenant, les effets de toute la commande:

/tmp/user$ echo $(cd ~ && pwd)
/home/user
/tmp/user$ _

Le répertoire a été modifié, mais à l'intérieur d'un sous-shell $(…), le répertoire modifié est imprimé /home/user, mais est immédiatement supprimé à la fermeture du sous-shell. Le pwd redevient le répertoire initial ( /tmp/user).

|

Voici ce qui se passe:

/tmp/user$ cd ~ | pwd
/tmp/user
/tmp/user$ _

Le méta-caractère |(pas un véritable opérateur) signale au shell de créer ce qu'on appelle un "Pipe", (en bash) chaque commande de chaque côté du pipe ( |) est définie à l'intérieur de chaque sous-shell, d'abord le côté droit commande, puis, celui de gauche. Le descripteur de fichier d'entrée ( /dev/stdin) de la commande de droite est connecté au descripteur de sortie ( /dev/stdout), puis les deux commandes sont démarrées et laissées pour interagir. La commande gauche ( cd -) n'a pas de sortie et, également, la commande droite ( pwd) n'accepte aucune entrée. Ainsi, chacun s'exécute indépendamment à l'intérieur de chaque sous-shell.

  • Le cd ~change le pwd d'un shell.
  • Le pwdimprime le (complètement indépendant) pwd de l'autre sous-shell.

Les modifications sur chaque coque sont rejetées lorsque le tuyau se termine, la sous-coque externe n'a pas changé le pwd.

C'est pourquoi les deux commandes ne sont connectées que par des "descripteurs de fichiers".
Dans ce cas, rien n'est envoyé et rien n'est lu.

La commande entière:

$ echo "$(cd ~ | pwd)"

Imprime simplement le répertoire où la commande a été exécutée.

Sorontar
la source
5

Je ne sais pas si tu voulais dire '|' ou '||' dans votre deuxième cas.

«|» dans un shell dirige la sortie d'une commande vers l'entrée d'une autre - un cas d'utilisation courant ressemble à: curl http://abcd.com/efgh | grep ijkl exécuter une commande et utiliser une autre commande pour traiter la sortie d'une commande.

Dans l'exemple que vous donnez, il est assez absurde, car «cd» ne génère généralement aucune sortie et «pwd» n'attend aucune entrée.

«&&» et «||» sont des commandes partenaires cependant. Ils sont conçus pour être utilisés de la même manière que les opérateurs logiques "et" et "ou" dans la plupart des langues. Cependant, les optimisations qui sont effectuées leur donnent un comportement spécifique qui est un paradigme de programmation shell.

Pour déterminer le résultat d'une opération logique "et", il vous suffit d'évaluer la deuxième condition si la première condition réussit - si la première condition échoue, le résultat global sera toujours faux.

Pour déterminer le résultat d'une opération logique "ou", il vous suffit d'évaluer la deuxième condition si la première condition échoue - si la première condition réussit, le résultat global sera toujours vrai.

Donc, dans le shell, si vous command1 && command2 command2ne serez exécuté que lorsque vous aurez command1terminé et retourné un code de résultat réussi. Si vous avez command1 || command2 command2sera exécuté à la command1fin si command1renvoie un code d'échec.

Un autre paradigme commun est d'avoir command1une commande de test - cela génère une seule instruction if / then de ligne - par exemple:

[ "$VAR" = "" ] && VAR="Value if empty"

Est une manière (longue) d'affecter une valeur à une variable si elle est actuellement vide.

Il existe de nombreux exemples d'utilisation de ce processus ailleurs sur Stack Exchange

Michael Firth
la source