¿Cómo diferencia bash entre la expansión de llaves y la agrupación de comandos?

48

He notado que {se puede usar en la expansión de llaves:

echo {1..8}

o en la agrupación de comandos:

{ls;echo hi}

¿Cómo sabe bash la diferencia?

primavera amorosa
fuente
1
Excelente pregunta, +1. Parece que podría {interpretarse como una lista de comandos si aparece al comienzo de un comando y, de lo contrario, como una expansión de llaves, pero no estoy seguro.
Celada
16
{ls;echo hi}No es legal bash. Necesita un espacio después de la llave de apertura y un punto y coma antes de la de cierre.
PSkocik

Respuestas:

39

Una razón simplificada es la existencia de un carácter: space.

Las expansiones de llaves no procesan espacios (sin comillas).

Una {...}lista necesita espacios (sin comillas).

La respuesta más detallada es cómo el shell analiza una línea de comando .


El primer paso para analizar (comprender) una línea de comando es dividirla en partes.
Estas partes (generalmente llamadas palabras o tokens) resultan de dividir una línea de comando en cada metacarácter del enlace :

  1. Divide el comando en tokens que están separados por el conjunto fijo de metacaracteres: ESPACIO, TAB, NUEVA LÍNEA,;, (,), <,>, | y &. Los tipos de tokens incluyen palabras, palabras clave, redireccionadores de E / S y punto y coma.

Metacaracteres: spacetabenter;,<>|y &.

Después de dividir, las palabras pueden ser de un tipo (tal como lo entiende el shell):

  • Comando pre-asignados: LC=ALL ...
  • Mando LC=ALL echo
  • Argumentos LC=ALL echo "hello"
  • Redireccionamiento LC=ALL echo "hello" >&2

Expansión de la llave

Solo si una "cadena de llaves" (sin espacios o metacaracteres) es una sola palabra (como se describe anteriormente) y no se cita , es un candidato para la "expansión de llaves". Más controles se realizan en la estructura interna más adelante.

Por lo tanto, esto: {ls,-l}califica como "expansión de llaves" para convertirse ls -l, ya sea como first wordo argument(en bash, zsh es diferente).

$ {ls,-l}            ### executes `ls -l`
$ echo {ls,-l}       ### prints `ls -l`

Pero esto no: {ls ,-l}. Bash se dividirá spacey analizará la línea como dos palabras: {lsy ,-l}que activará a command not found( ,-l}se pierde el argumento ):

 $ {ls ,-l}
 bash: {ls: command not found

Su línea: {ls;echo hi}no se convertirá en una "expansión de llaves" debido a los dos metacaracteres ;y space.

Se divide en tres partes esta: {lsnuevo comando: echo hi}. Comprenda que ;desencadena el inicio de un nuevo comando. El comando {lsno se encuentra, y el siguiente comando imprimirá hi}:

$ {ls;echo hi}
bash: {ls: command not found
hi}

Si se coloca después de algún otro comando, de todos modos comenzará un nuevo comando después de ;:

$ echo {ls;echo hi}
{ls
hi}

Lista

Uno de los "comandos compuestos" es una "Lista Brace" (mis palabras): { list; }.
Como puede ver, se define con espacios y un cierre ;.
Los espacios y ;son necesarios porque ambos {y }son " palabras reservadas ".

Y por lo tanto, para ser reconocido como palabras, debe estar rodeado de metacaracteres (casi siempre:) space.

Como se describe en el punto 2 de la página vinculada

  1. Comprueba el primer token de cada comando para ver si es ..., {o (entonces el comando es en realidad un comando compuesto.

Tu ejemplo: {ls;echo hi}no es una lista.

Necesita un cierre ;y un espacio (al menos) después {. El último }está definido por el cierre ;.

Esta es una lista { ls;echo hi; }. Y esto { ls;echo hi;}también es (menos utilizado, pero válido) (Gracias @choroba por la ayuda).

$ { ls;echo hi; }
A-list-of-files
hi

Pero como argumento (el shell conoce la diferencia) a un comando, desencadena un error:

$ echo { ls;echo hi; }
bash: syntax error near unexpected token `}'

Pero tenga cuidado con lo que cree que el shell está analizando:

$ echo { ls;echo hi;
{ ls
hi

fuente
2
¡esta es realmente la mejor respuesta, porque nos da cómo funciona bash parser! y con una explicación detallada!
lovespring
2
No necesitas el espacio entre ;y }. { ls;}funciona ya que el punto y coma ya es un meta-personaje.
choroba
1
@lovespring Gracias, sí, invertí algo de tiempo en escribirlo. Me alegra saber que es útil. Gracias otra véz.
excelente artículo, muchas gracias por las referencias
Edward Torvalds
16

El bloque {es una palabra clave de shell, por lo que debe estar separado de la siguiente palabra por espacio, mientras que en la expansión de llaves, no debe haber espacio (si necesita llaves para expandir un espacio, debe escapar de él echo {\ ,a}{b,c}).

Puede usar la expansión de llaves al comienzo de un comando:

{ls,.}  # expands to "ls ."

Sin embargo, no puede usarlo para expandir a un bloque, ya que el análisis de los comandos de agrupación ocurre antes de las expansiones:

echo {'{ ls','.;}'}  # { ls .;}
{'{ ls','.;}'}       # bash: { ls: No such file or directory
choroba
fuente
5

Lo sabe comprobando la sintaxis de la línea de comando. Del mismo modo, sabe que en la expresión echo echo, el primer eco debe tratarse como un comando y el segundo eco como un parámetro del primer eco.

En bash es muy simple, ya que { cmd; }debe tener espacios y punto y coma. Sin embargo, por ejemplo, en zsh no son necesarios, pero aún analizando el contexto de {}shell es capaz de decir qué se debe hacer con su contenido.

Considera lo siguiente:

alias 1..3=date
{ 1..3; }    #in bash
{1..3}       #in zsh

Ambos devuelven la fecha actual, pero

echo {1..3}

devuelve 1 2 3porque shell conoce {}en un argumento para comando echo, por lo que debe expandirse.

jimmij
fuente
{seguido de un espacio sin comillas no comienza la expansión de llaves en bash.
choroba
@ choroba Sí, y no solo justo después {. El espacio sin comillas no puede estar en ninguna parte porque el shell divide toda la línea de comando en los espacios.
jimmij
0

En primer lugar, una llave compuesta debe ser una palabra en sí misma y la primera palabra de la línea de comando:

echo { these braces are just words }

En segundo lugar, los brackets individuales no son especiales (como puede ver arriba). Las llaves vacías tampoco son especiales:

echo {} # just the token {}: familiar from the find command

Cualquier cosa sin comas también es solo

echo {abc} # just {abc}

Aquí es donde comienza la acción.

echo {a,b} # braces disappear, a b results.

Entonces, básicamente, para que se inicie la expansión de llaves, necesitamos una sola palabra (no separada en campos en el espacio en blanco), dentro de la cual se produce al menos una instancia de {...}dentro de la cual se produce al menos una coma.

Esta puede ser la primera palabra en la línea de comando, por cierto:

{ls,-l} .   # just "ls -l ."
Kaz
fuente