Precedencia de Pipe (|) y lógica y (&&) en bash

17

El escenario clásico con precedencia de operador, tiene una línea como:

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

Y no sabes si está analizado ((A && B) | C)o (A && B | C)...

La documentación casi oficial que se encuentra aquí no enumera la tubería en la lista, por lo que no puedo simplemente verificar en la tabla.

Además, en bash, (no es solo para cambiar el orden de las operaciones, sino que crea una subshell , por lo que no estoy 100% seguro de que estas líneas sean el equivalente de la línea anterior:

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

En términos más generales, ¿cómo saber el AST de una línea bash? En python tengo una función que me da el árbol para que pueda verificar fácilmente el orden de operación.

Robert Vanden Eynde
fuente
1
Puede ser útil saber que |es solo un conector que se ajusta a la salida estándar LHS a la entrada estándar RHS. Entonces, si el cdcomando en su ejemplo falla, no enviaría ninguna salida head, pero headde hecho aún executeanalizaría la nada y no devolvería ninguna salida.
DopeGhoti
1
bashestá usando un analizador yacc no algo ad-hoc; si lo ejecuta yacc -v, obtendrá y.outputuna gramática agradable que lo muestra &&y ||combina listas, y las listas finalmente se componen de tuberías (y no al revés); tl; dr; A && B | Ces lo mismo que A && { B | C; }, como se esperaba. No asuma ningún orden de ejecución entre By C; Los comandos en una tubería se ejecutan en paralelo .
mosvy
1
Tenga en cuenta que esa "documentación casi oficial" a la que apunta es completamente irrelevante, ya que se trata de los operadores utilizados dentro de las [...]pruebas y $((...))evaluaciones aritméticas; en particular, ||y &&como se usa con la lista de comandos en el lenguaje de shell tiene la misma precedencia , a diferencia de los operadores respectivos en Co en la evaluación aritmética (donde se &&une más estrechamente que ||).
mosvy
@mosvy, ese documento no tiene ni siquiera decir qué contexto se aplica en la tabla, por lo que parece menos útil en ese sentido, también ...
ilkkachu

Respuestas:

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

Esto es equivalente a

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

(los comandos del grupo de llaves se unen sin una subshell ). Por lo tanto, la precedencia de |es mayor (se une más fuerte) que &&y ||. Es decir,

A && B | C

y

A || B | C

siempre significa que sólo salida de B es que debe darse a C . Puede usar (...)o { ... ; }para unir comandos como una sola entidad para la desambiguación si es necesario:

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

Puede probar esto usando algunos comandos diferentes. Si tu corres

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

entonces obtendrás

hello
WORLD

atrás: tr a-z A-Zmayúsculas su entrada , y puede ver que solo echo worldse canalizó en él, mientrasecho hello realizó por sí solo.


Esto se define en la gramática de la cáscara , aunque no está terriblemente claro: la and_orproducción (para &&/ ||) se define para tener aa pipelineen su cuerpo, mientras que pipelinesolo contiene command, que no contiene and_or, solo la complete_commandproducción puede alcanzar and_or, y solo existe en el nivel superior y dentro de los cuerpos de construcciones estructurales como funciones y bucles.

Puede aplicar manualmente esa gramática para obtener un árbol de análisis para un comando, pero Bash no proporciona nada en sí. No conozco ningún shell que vaya más allá de lo que se usa para su propio análisis.

La gramática de shell tiene muchos casos especiales definidos solo semi-formalmente y puede ser una misión acertada. Incluso Bash mismo a veces se ha equivocado , por lo que los aspectos prácticos y el ideal pueden ser diferentes.

Hay analizadores externos que intentan hacer coincidir la sintaxis y producir un árbol, y de ellos recomendaré ampliamente Morbig , que intenta ser el más confiable.

Michael Homer
fuente
7

TL; DR : separadores de listas como ;. &,&& y ||decida el orden de análisis.

El manual de bash nos dice:

Las listas AND y OR son secuencias de una o más tuberías separadas por && y || operadores de control, respectivamente.

O cómo la wiki de Bash Hacker lo expuso sucintamente

<PIPELINE1> && <PIPELINE2>

Por lo tanto, cd ~/screenshots/ && ls screenshot* | head -n 5 hay una tubería, ls screenshot* | head -n 5y un comando simple cd ~/screenshots/. Tenga en cuenta que de acuerdo con el manual

Cada comando en una tubería se ejecuta como un proceso separado (es decir, en una subshell).

Por otro lado, (cd ~/screenshots/ && ls screenshot*) | head -n 5es diferente: tiene una tubería: a la izquierda hay un subshell y a la derecha tiene head -n 5. En este caso, usando la notación de OP sería(A && B) | C


Tomemos otro ejemplo:

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

Aquí tenemos una lista <pipeline1> && <pipeline2>. Como sabemos que el estado de salida de la tubería es el mismo que el del último comando y falsedevuelve el estado negativo, también conocido como falla, &&no se ejecutará en el lado derecho.

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

Aquí la tubería izquierda tiene un estado de salida exitoso, por lo que se ejecuta la tubería derecha y vemos su salida.


Tenga en cuenta que la gramática de shell no implica el orden de ejecución real. Para citar una de las respuestas de Gilles :

Los comandos canalizados se ejecutan simultáneamente. Cuando ejecutas ps | grep ..., es la suerte del sorteo (o una cuestión de detalles del funcionamiento del shell combinado con el ajuste del programador en las entrañas del núcleo) en cuanto a si ps o grep comienzan primero, y en cualquier caso continúan para ejecutar simultáneamente.

Y del manual bash:

Las listas AND y OR se ejecutan con asociatividad izquierda.

Sobre la base de que en cd ~/screenshots/ && ls screenshot* | head -n 5el cd ~/screenshots/se ejecutaría en primer lugar, ls screenshot* | head -n 5si el comando anterior tiene éxito, pero head -n 5puede ser un primer proceso dio lugar lsya que están en una tubería.

Sergiy Kolodyazhnyy
fuente
1
No veo dónde la pregunta pregunta nada sobre el orden en que se generan las cosas.
Michael Homer el
"no hay precedencia de cuál se genera primero, cd ~/screenshots/ && ls screenshot*o head -n 5" Bueno, sí, ese es el caso ((cd ~/screenshots/ && ls screenshot*) | head -n 5). Pero no dice nada sobre el caso ambiguo cd ~/screenshots/ && ls screenshot* | head -n 5que parece ser el punto de la pregunta (con o sin paréntesis).
ilkkachu
@ilkkachu Correcto, pero las siguientes oraciones se refieren a eso. cd ~/screenshots/ && ls screenshot* tendría que procesarse primero porque las &&listas son más altas en el orden de precedencia (basado en la respuesta de l0b0, al menos). ¿Dónde crees que debería mejorar la respuesta?
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy, bueno, dado que la pregunta tiene ambas (cd && ls | head)y ((cd && ls) | head), podría ser bueno ser explícito sobre a qué te refieres.
ilkkachu
@ilkkachu OK, voy a eliminar esto por ahora, y editarlo mientras tanto
Sergiy Kolodyazhnyy
3

Aquí es donde se especifica en 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>.

Entonces, &&separa las tuberías.

JoL
fuente
2

Podrías intentarlo echo hello && echo world | less. Verá que |tiene mayor prioridad (una tubería de comandos es un comando). Para su segundo ejemplo, NO es lo mismo. Sin embargo, debido a que cdno tiene salida, no verá ninguna diferencia, vía ether.

ctrl-alt-delor
fuente