Priorité de Pipe (|) et logique et (&&) dans bash

17

Le scénario classique avec Operator Precedence, vous avez une ligne comme:

(cd ~/screenshots/ && ls screenshot* | head -n 5)

Et vous ne savez pas si c'est analysé ((A && B) | C)ou (A && B | C)...

La documentation presque officielle trouvée ici ne répertorie pas le canal dans la liste, je ne peux donc pas simplement vérifier dans le tableau.

De plus, en bash, ce (n'est pas seulement pour changer l'ordre des opérations mais cela crée un sous-shell , donc je ne suis pas sûr à 100% que cette ligne est l'équivalent de la ligne précédente:

((cd ~/screenshots/ && ls screenshot*) | head -n 5)

Plus généralement, comment connaître l' AST d'une ligne bash? En python, j'ai une fonction qui me donne l'arbre afin que je puisse facilement vérifier l'ordre des opérations.

Robert Vanden Eynde
la source
1
Il peut être utile de savoir qu'il ne |s'agit que d'un connecteur qui adapte la sortie standard LHS à la sortie standard RHS. Donc, si la cdcommande de votre exemple échoue, elle n'enverra aucune sortie à head, mais headen fait execute, analysera le rien et ne retournera aucune sortie.
DopeGhoti
1
bashutilise un analyseur yacc pas quelque chose d'ad-hoc; si vous exécutez cela, yacc -vcela vous donnera y.outputune belle grammaire qui montre cela &&et ||combine des listes, et les listes sont finalement constituées de pipelines (et non l'inverse); tl; dr; A && B | Cest le même que A && { B | C; }, comme prévu. N'assumez aucun ordre d'exécution entre Bet C; les commandes d'un pipeline sont exécutées en parallèle .
mosvy
1
Notez que cette "documentation presque officielle" que vous pointez est complètement hors de propos, car il s'agit des opérateurs utilisés dans les [...]tests et $((...))les évaluations arithmétiques; en particulier, ||et &&comme utilisé avec la liste des commandes dans le langage shell ont la même priorité , contrairement aux opérateurs respectifs dans Cou dans l'évaluation arithmétique (où &&se lie plus étroitement que ||).
mosvy
@mosvy, ce document ne dit même pas dans quel contexte le tableau s'applique, il semble donc moins qu'utile dans ce sens aussi ...
ilkkachu

Réponses:

21
cd ~/screenshots/ && ls screenshot* | head -n 5

Cela équivaut à

cd ~/screenshots && { ls screenshot* | head -n 5 ; }

(le groupe d'accolades commande ensemble sans sous-coque ). La priorité de |est donc plus élevée (se resserre) que &&et ||. C'est,

A && B | C

et

A || B | C

signifie toujours que la sortie ne B doit être donnée à C . Vous pouvez utiliser (...)ou { ... ; }pour joindre des commandes ensemble en une seule entité pour la désambiguïsation si nécessaire:

{ A && B ; } | C
A && { B | C ; } # This is the default, but you might sometimes want to be explicit

Vous pouvez tester cela à l'aide de différentes commandes. Si vous courez

echo hello && echo world | tr a-z A-Z

alors vous aurez

hello
WORLD

retour: en tr a-z A-Zmajuscules son entrée , et vous pouvez voir que seul y echo worldétait canalisé, tout en étant echo hellopassé par lui-même.


Ceci est défini dans la grammaire du shell , bien que ce ne soit pas très clair: la and_orproduction (pour &&/ ||) est définie pour avoir aa pipelinedans son corps, alors qu'elle pipelinecontient juste command, qui ne contient pasand_or - seule la complete_commandproduction peut atteindre and_or, et elle n'existe qu'au niveau supérieur et à l'intérieur des corps des constructions structurelles comme les fonctions et les boucles.

Vous pouvez appliquer manuellement cette grammaire pour obtenir un arbre d'analyse pour une commande, mais Bash ne fournit rien lui-même. Je ne connais aucun shell qui fasse au-delà de ce qui est utilisé pour leur propre analyse.

La grammaire shell a beaucoup de cas spéciaux définis uniquement de manière semi-formelle et il peut être assez difficile de bien faire les choses. Même Bash lui-même s'est parfois trompé , de sorte que les aspects pratiques et l'idéal peuvent être différents.

Il existe des analyseurs externes qui tentent de faire correspondre la syntaxe et de produire un arbre, et parmi ceux-ci, je recommanderai largement Morbig , qui tente d'être le plus fiable.

Michael Homer
la source
7

TL; DR : liste les séparateurs tels que ;. &, &&et ||décidez de l'ordre d'analyse.

Le manuel bash nous dit:

Les listes ET et OU sont des séquences d'un ou plusieurs pipelines séparés par les symboles && et || opérateurs de contrôle, respectivement.

Ou comment le wiki de Bash Hacker a dit succinctement

<PIPELINE1> && <PIPELINE2>

Ainsi, cd ~/screenshots/ && ls screenshot* | head -n 5 il y a un pipeline - ls screenshot* | head -n 5et une commande simple cd ~/screenshots/. Notez que selon le manuel

Chaque commande d'un pipeline est exécutée comme un processus distinct (c'est-à-dire dans un sous-shell).

En revanche, (cd ~/screenshots/ && ls screenshot*) | head -n 5c'est différent - vous avez un pipeline: à gauche il y a un sous-shell et à droite vous en avez head -n 5. Dans ce cas, en utilisant la notation OP, il serait(A && B) | C


Prenons un autre exemple:

$ echo foo | false &&  echo 123 | tr 2 5
$

Ici, nous avons une liste <pipeline1> && <pipeline2>. Puisque nous savons que l'état de sortie du pipeline est le même que celui de la dernière commande et falserenvoie un état négatif, c'est-à-dire échouer, &&ne s'exécutera pas à droite.

$ echo foo | true &&  echo 123 | tr 2 5
153

Ici, le pipeline de gauche a un statut de sortie réussi, donc le pipeline de droite est exécuté et nous voyons sa sortie.


Notez que la grammaire du shell n'implique pas l'ordre d'exécution réel. Pour citer une des réponses de Gilles :

Les commandes canalisées s'exécutent simultanément. Lorsque vous exécutez ps | grep…, c'est la chance du tirage au sort (ou une question de détails du fonctionnement de la coque combinée avec un réglage fin du programmateur au fond des entrailles du noyau) pour savoir si ps ou grep commence en premier, et en tout cas ils continuent à exécuter simultanément.

Et du manuel bash:

Les listes ET et OU sont exécutées avec l'associativité gauche.

Sur la base de cela, cd ~/screenshots/ && ls screenshot* | head -n 5le cd ~/screenshots/serait exécuté en premier, ls screenshot* | head -n 5si la commande précédente réussit, mais head -n 5peut être un premier processus généré plutôt que lscar ils sont dans un pipeline.

Sergiy Kolodyazhnyy
la source
1
Je ne vois pas où la question demande quoi que ce soit sur l'ordre dans lequel les choses sont générées.
Michael Homer
"il n'y a pas de précédent dont l'un apparaît en premier, cd ~/screenshots/ && ls screenshot*ou head -n 5" Eh bien, oui, c'est le cas ((cd ~/screenshots/ && ls screenshot*) | head -n 5). Mais cela ne dit rien sur le cas ambigu cd ~/screenshots/ && ls screenshot* | head -n 5qui semble être le point de la question (avec ou sans parenthèses environnantes).
ilkkachu
@ilkkachu C'est vrai, mais les phrases suivantes en parlent. cd ~/screenshots/ && ls screenshot* devrait être traité en premier car les &&listes sont plus élevées dans l'ordre de priorité (sur la base de la réponse de l0b0, au moins). Où pensez-vous que je devrais améliorer la réponse?
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy, eh bien, étant donné que la question a les deux (cd && ls | head)et ((cd && ls) | head), il pourrait être bon d'être explicite sur laquelle vous voulez dire.
ilkkachu
@ilkkachu OK, je vais supprimer cela pour l'instant et le modifier en attendant
Sergiy Kolodyazhnyy
3

Voici où il est spécifié dans bash(1):

SHELL GRAMMAR
[...]
   Pipelines
       A  pipeline  is  a sequence of one or more commands separated by one of
       the control operators | or |&.
[...]
   Lists
       A list is a sequence of one or more pipelines separated by one  of  the
       operators ;, &, &&, or ||, and optionally terminated by one of ;, &, or
       <newline>.

Donc, &&sépare les pipelines.

JoL
la source
2

Vous pourriez simplement essayer echo hello && echo world | less. Vous verrez que la |priorité est plus élevée (un pipeline de commandes est une commande). Là pour votre 2ème exemple n'est PAS le même. Cependant, parce qu'il cdn'a pas de sortie, vous ne verrez aucune différence, de façon éthérée.

ctrl-alt-delor
la source