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

8

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 es generado por un comando incluido $(), solo uno de los patrones funciona:

$ 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?

Herosław Miraszewski
fuente
44
La expansión de llaves no se realiza entre comillas simples o dobles
Sergiy Kolodyazhnyy
3
@SergiyKolodyazhnyy El punto de la cuestión es que el ?está también citado, y consigue expandieron después de $(...)lo sustituye, pero la expansión de llaves no.
Michael Homer
1
@muru No, ese no es el mismo problema. Aquí el orden de las expansiones no importa, lo que importa es qué expansión tiene lugar en qué contexto. No me sorprendería si esta pregunta fuera un duplicado, pero no pude encontrarla.
Gilles 'SO- deja de ser malvado'
1
@mosvy Ksh y bash hacen expansiones en el mismo orden, pero ksh hace la expansión en un caso donde bash no lo hace en absoluto. Zsh-with-globsubst hace las mismas expansiones que bash, pero en un orden diferente.
Gilles 'SO- deja de ser malvado'
1
@Gilles no, no lo hacen. Como se documenta y se demuestra fácilmente, ksh (y zsh) realizará la expansión de la llave justo antes de la aplicación global. zsh-con-globsubst no realizará ninguna expansión de llaves en absoluto en los resultados de $-expansions: zsh -o globsubst -c 'a=/e*; b={/b*,/v*}; echo $a; echo $b'.
mosvy

Respuestas:

17

Es una combinación de dos cosas. Primero, la expansión de llaves no es un patrón que coincida con los nombres de los archivos: es una sustitución puramente textual. Consulte ¿Cuál es la diferencia entre `a [bc] d` (paréntesis) y` a {b, c} d` (llaves)? . En segundo lugar, cuando usa el resultado de una sustitución de comando fuera de las comillas dobles ( ls $(…)), lo que sucede es solo la coincidencia de patrones (y la división de palabras: el operador "split + glob"), no un 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, produciendo una lista que consiste en una sola cadena test?.txtya que no hay caracteres de espacio en blanco (más precisamente, no hay caracteres $IFS) en ella. 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 archivo que coinciden con los patrones, produciendo la lista de dos elementos que contienetest1.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, produciendo una lista que consiste en una sola cadena test{1,2}. Cada elemento de esta lista de un elemento experimenta una expansión de comodín condicional, que no hace nada (el elemento se deja como está) ya que no hay caracteres comodín en la cadena. Así lsse llama con el argumento único 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 sufre división de palabras, produciendo dos cadenas test1y test2. Luego, dado que ninguna de las cadenas contiene un carácter comodín, se dejan solas, por lo que lsse llama con dos argumentos test1y test2.

Gilles 'SO- deja de ser malvado'
fuente
3
Tenga en cuenta que pdksh y ksh93 sí realizan la expansión de los brackets en las expansiones (antes del glob; no con noglob, sino en el caso de ksh93, ¡aún cuando braceexpand está apagado!)
Stéphane Chazelas
Parece que te olvidaste .txten la segunda explicación.
Val dice Reinstate Monica
10

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 (hecho de izquierda a derecha); división de palabras; y expansión de nombre de archivo.

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

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

Su resultado es:

1lala 2lala
dedowsdi
fuente
6

Ese problema es muy específico bashy es porque decidieron bashseparar la expansión de la llave de la expansión del nombre de archivo (globbing) y realizarla primero, antes de todas las otras expansiones.

Desde la página del bashmanual:

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 (hecho de izquierda a derecha); división de palabras; y expansión de nombre de ruta.

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

Esto es diferente de todos los otros 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, pero no se limita a, cshdónde se inventaron las expansiones de llaves.

$ 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

Este último ejemplo es el mismo en csh, zsh, ksh93, mksho fish.

Además, observe que la expansión de llaves como parte de globbing tambié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>'.

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

Mosvy
fuente
3
Tenga en cuenta que perlsolía invocar cshpara expandir globs, por lo que no es sorprendente que todavía reconozca los mismos operadores de globbing quecsh
Stéphane Chazelas
1

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 un patrón coincidente, sino que debe tomarse literalmente como período aquí.

mkzia
fuente
(1) La pregunta es: "¿Qué está pasando aquí? ¿Por qué [el] patrón {1,2}"se comporta como lo hace? La pregunta no es "¿Cómo puedo usar un comando {1,2}para comportarme de la manera en que ?funciona?" Estás respondiendo la pregunta equivocada. (2) La barra invertida no tiene nada que ver con eso. Su comando funciona de la manera en que lo hace porque ha eliminado las comillas que estaban en el comando en la pregunta.
G-Man dice 'Restablece a Monica' el
0

Funciona si eliminas las comillas

$ ls $(echo test{1,2})
test1  test2
darxmurf
fuente
99
La expansión ahora está ocurriendo antes de la sustitución del comando, lo que no creo que sea lo que está preguntando (compare con lo que está sucediendo ?).
Michael Homer