¿Por qué {1,2} impreso por un comando en $() no se interpola?

¿Por qué {1,2} impreso por un comando en $() no se interpola?

Estoy en un directorio en el que tengo dos archivos de texto:

$ touch test1.txt
$ touch test2.txt

Cuando intento enumerar los archivos (con Bash) usando algún patrón, funciona:

$ ls test?.txt
test1.txt  test2.txt
$ ls test{1,2}.txt
test1.txt  test2.txt

Sin embargo, cuando un patrón se produce mediante un comando incluido en $(), solo funciona uno de los patrones:

$ ls $(echo 'test?.txt')
test1.txt  test2.txt
$ ls $(echo 'test{1,2}.txt')
ls: cannot access test{1,2}.txt: No such file or directory

¿Que está pasando aqui? ¿Por qué el patrón {1,2}no funciona?

Respuesta1

Es una combinación de dos cosas. Primero, la expansión de llaves no es un patrón que coincida con los nombres de archivos: es una sustitución puramente textual; consulte¿Cuál es la diferencia entre `a[bc]d` (corchetes) y `a{b,c}d` (llaves)?. En segundo lugar, cuando utiliza el resultado de una sustitución de comando fuera de las comillas dobles ( ls $(…)), lo que sucede es solo una coincidencia de patrones (y división de palabras: el operador “split+glob”), no un nuevo análisis completo.

Con ls $(echo 'test?.txt'), el comando echo 'test?.txt'genera la cadena test?.txt(con una nueva línea final). La sustitución del comando da como resultado la cadena test?.txt(sin una nueva línea final, porque la sustitución del comando elimina las nuevas líneas finales). Esta sustitución sin comillas se divide en palabras, lo que produce una lista que consta de una sola cadena, test?.txtya que no contiene espacios en blanco (más precisamente, ningún carácter en $IFS). Cada elemento de esta lista de un elemento se somete a una expansión de comodín condicional y, dado que hay un carácter comodín ?en la cadena, la expansión de comodín ocurre. Dado que el patrón test?.txtcoincide con al menos un nombre de archivo, el elemento de la lista test?.txtse reemplaza por la lista de nombres de archivos que coinciden con los patrones, lo que produce la lista de dos elementos que contiene test1.txty test2.txt. Finalmente lsse llama con dos argumentos test1y test2.

Con ls $(echo 'test{1,2}'), el comando echo 'test{1,2}'genera la cadena test{1,2}(con una nueva línea final). La sustitución del comando da como resultado la cadena test{1,2}. Esta sustitución sin comillas se divide en palabras, lo que produce una lista que consta de una sola cadena test{1,2}. Cada elemento de esta lista de un elemento se somete a una expansión de comodín condicional, que no hace nada (el elemento se deja como está) ya que no hay ningún carácter comodín en la cadena. Así lsse llama con el único argumento test{1,2}.

A modo de comparación, esto es lo que sucede con ls $(echo test{1,2}). El comando echo test{1,2}genera la cadena test1 test2(con una nueva línea final). La sustitución del comando da como resultado la cadena test1 test2(sin una nueva línea final). Esta sustitución sin comillas se divide en palabras, lo que produce dos cadenas test1y test2. Luego, como ninguna de las cadenas contiene un carácter comodín, se dejan solas, por lo que lsse llama con dos argumentos test1y test2.

Respuesta2

El orden de las expansiones es: expansión de llaves; expansión de tilde, expansión de parámetros y variables, expansión aritmética y sustitución de comandos (realizada de izquierda a derecha); división de palabras; y expansión del nombre de archivo.

La expansión de la llave no ocurrirá después de la sustitución del comando. Puedes usar eval para forzar otra ronda de expansión:

eval echo $(echo '{1,2}lala')

Su resultado es:

1lala 2lala

Respuesta3

Ese problema es muy específico de bash, y se debe a que decidieron bashseparar la expansión de llaves de la expansión del nombre de archivo (globbing) y realizarla primero, antes que todas las demás expansiones.

Desde la bashpágina de manual:

El orden de las expansiones es: expansión de llaves; expansión de tilde, expansión de parámetros y variables, expansión aritmética y sustitución de comandos (realizada de izquierda a derecha); división de palabras; y expansión del nombre de ruta.

En su ejemplo, bashsolo verá sus llaves después de haber realizado la sustitución del comando (el $(echo ...)), cuando ya sea demasiado tarde.

Esto es diferente de todos los demás shells, que realizan la expansión de llaves justo antes (y algunos incluso como parte de) la expansión del nombre de ruta (globbing). Eso incluye, entre otros, cshdónde se inventaron por primera vez las expansiones de tirantes.

$ csh -c 'ls `echo "test{1,2}.txt"`'
test1.txt test2.txt
$ ksh -c 'ls $(echo "test{1,2}.txt")'
test1.txt  test2.txt

$ var=nope var1=one var2=two bash -c 'echo $var{1,2}'
one two
$ var=nope var1=one var2=two csh -c 'echo $var{1,2}'
nope1 nope2

El último ejemplo es el mismo en csh, zsh, ksh93o mksh.fish

Además, observe que la expansión de la llavecomo parte del globbingtambién está disponible a través de la glob(3)función de biblioteca (al menos en Linux y todos los BSD) y en otras implementaciones independientes (por ejemplo, en perl:) perl -le 'print join " ", <test{1,2}.txt>'.

Probablemente haya una historia detrás de por qué se hizo de manera diferente bash, pero FWIW no pude encontrar ninguna explicación lógica y encuentro que todas las racionalizaciones post-hoc no son convincentes.

Respuesta4

Por favor, inténtalo:::

ls $(prueba de eco{1,2}\.txt)

Con una barra invertida. Ahora funciona. También elimine lo que decía el cartel anterior, las comillas. El punto no es para hacer coincidir el patrón, sino que aquí debe tomarse literalmente como punto.

información relacionada