Comportamiento inesperado con eco [[: dígito:]]

10

Me gustaría preguntar:

¿Por qué se echo {1,2,3}expande a 1 2 3, que es un comportamiento esperado, mientras que echo [[:digit:]]regresa [[:digit:]]mientras esperaba que imprimiera todos los dígitos de 0a 9?

AbdAllah Talaat
fuente
Cuidado con: unix.stackexchange.com/q/347950/117549
Jeff Schaller

Respuestas:

34

Porque son dos cosas diferentes. El {1,2,3}es un ejemplo de expansión de llaves . El armazón{1,2,3} expande la construcción , incluso antes de verla. Puede ver qué sucede si usa :echoset -x

$ set -x
$ echo {1,2,3}
+ echo 1 2 3
1 2 3

Como puede ver, el comando echo {1,2,3}se expande para:

echo 1 2 3

Sin embargo, [[:digit:]]es una clase de caracteres POSIX . Cuando se lo da echo, el shell también lo procesa primero, pero esta vez se está procesando como un globo de shell . funciona de la misma manera que si ejecuta, echo *que imprimirá todos los archivos en el directorio actual. Pero [[:digit:]]es un globo de shell que coincidirá con cualquier dígito. Ahora, en bash, si un globo de shell no coincide con nada, se expandirá a sí mismo:

$ echo /this*matches*no*files
+ echo '/this*matches*no*files'
/this*matches*no*files

Si el globo coincide con algo, se imprimirá:

$ echo /e*c
+ echo /etc
/etc

En ambos casos, echosolo imprime lo que el shell le dice que imprima, pero en el segundo caso, ya que el globo coincide con algo ( /etc) se le dice que imprima ese algo.

Entonces, dado que no tiene ningún archivo o directorio cuyo nombre consista exactamente en un dígito (que es lo [[:digit:]]que coincidiría), el globo se expande a sí mismo y obtiene:

$ echo [[:digit:]]
[[:digit:]]

Ahora, intente crear un archivo llamado 5y ejecute el mismo comando:

$ echo [[:digit:]]
5

Y si hay más de un archivo coincidente:

$ touch 1 5       
$ echo [[:digit:]]
1 5

Esto está (más o menos) documentado en man bashla explicación de las nullglobopciones que desactiva este comportamiento:

nullglob
    If  set,  bash allows patterns which match no files (see
    Pathname Expansion above) to expand to  a  null  string,
    rather than themselves.

Si configura esta opción:

$ rm 1 5
$ shopt -s nullglob
$ echo [[:digit:]]  ## prints nothing

$ 
terdon
fuente
44
Consulte también shopt -s failglobpara obtener un comportamiento más útil similar al de los proyectiles modernos como zsho fish.
Stéphane Chazelas
Estoy de acuerdo con Stéphane, uso failglob. nullglobpuede causar problemas inesperados, por ejemplo, al pegar una URL que tiene un ?.
Kevin
1
Claro, solo mencioné nullglobpara demostrar que el patrón está siendo interpretado como un globo por el caparazón.
terdon
14

{1,2,3}es una expansión de llaves , se expande a las palabras enumeradas sin tener en cuenta su significado.

[...]es un grupo de caracteres, utilizado en la expansión de nombre de archivo (o comodín, o glob) de manera similar al asterisco *y al signo de interrogación ?. Coincide con cualquier carácter individual incluido en la lista, o con caracteres que son miembros de grupos con nombre, como [:digit:]si se enumeran. El comportamiento predeterminado de la mayoría de los shells es dejar el comodín como está si no hay archivos que lo coincidan.

(Tenga en cuenta que realmente no puede convertir un comodín / patrón en el conjunto de cadenas que coincidiría. El asterisco puede coincidir con cualquier cadena de cualquier longitud, por lo que expandir cualquier patrón que lo contenga produciría una lista infinita de cadenas).

Entonces:

$ bash -c 'echo [[:digit:]]'           # bash leaves it as-is
[[:digit:]]
$ zsh -c 'echo [[:digit:]]'            # zsh by default complains if no match
zsh:1: no matches found: [[:digit:]]
$ touch 1 3 d i g t
$ bash -c 'echo [[:digit:]]'           # now there are two matches
1 3                                    # note that d, i, g and t do NOT match

Pero aún:

$ bash -c 'echo {1,2,3}'
1 2 3

Ambos están expandidos por el shell , no importa si el comando que está ejecutando es ls, o echoo rm. También tenga en cuenta que si se cita cualquiera de esos, no se expandirán:

$ bash -c 'echo "[[:digit:]]"'         # even though matching files still exist
[[:digit:]]
$ bash -c 'echo "{1,2,3}"'
{1,2,3}
ilkkachu
fuente
gracias por su respuesta, soy nuevo en Linux, así que déjenme preguntarles cómo se relaciona echo con los archivos 1 3, su función es imprimir sus argumentos para que stdout no
busque
1
@AbdAllahTalaat esto no tiene nada que ver con echo, en realidad. El shell (por ejemplo, bash) se "expandirá" [[:digit:]] antes de pasarlo echo, por lo que echonunca ve [[:digit:]], solo ve 1 3. Puede ver esto en acción ejecutando set -xlo que imprimirá los comandos reales que se están ejecutando (ejecute set +xpara apagarlo nuevamente).
terdon
@AbdAllahTalaat, echono busca archivos, el shell sí, antes de ejecutar el echo.
ilkkachu
Especialmente porque creo que en DOS / Windows las utilidades expanden los comodines, no el shell. (
Tal
lo siento chicos, cambié la respuesta correcta a la respuesta de tedron porque su comentario contenía el significado de que bash es lo que el trabajo no hace eco ... su respuesta también contenía ese significado ... todos ustedes me ayudaron ... deseé poder poner respuesta correcta para todas sus respuestas y comentarios
AbdAllah Talaat
4

{1,2,3}(y, por ejemplo, {1..3}son expansiones de llaves . El shell las interpreta antes de la ejecución del comando.

[[:digit:]]es un token de coincidencia de patrones , pero no lo está utilizando en una ubicación con ningún archivo que coincida con ese patrón. Si usa una coincidencia de patrón que no tiene coincidencias, se expande a sí misma:

$ echo [[:digit:]]; touch 3; echo [[:digit:]]
[[:digit:]]
3
DopeGhoti
fuente
No, como indican correctamente las otras respuestas, el patrón se compara con los nombres de archivo.
Toby Speight