¿Por qué nullglob no es predeterminado?

61

En la mayoría de las conchas nullglobno es el valor predeterminado. Eso significa, por ejemplo, si ejecuta este comando

ls *

en un directorio vacío, expandirá el *globo a un literal *, en lugar de a una lista vacía de argumentos. Hay formas de cambiar ese comportamiento, de modo que *en un directorio vacío devolverá una lista vacía de argumentos, lo que parecería más intuitivo.

Entonces, ¿hay alguna razón por la que nullglobestá deshabilitada de forma predeterminada? Si es así, ¿cuál es esa razón?

Dakkaron
fuente
13
Alguien una vez hizo una mala elección que se convirtió en "siempre lo hemos hecho de esta manera". Un fenómeno muy ubicuo (no solo) en el mundo del software.
PSkocik
44
La razón original es que la opción nullglob no existía en ese entonces. Por lo tanto, para mantener la compatibilidad con versiones anteriores, debe deshabilitarse de forma predeterminada.
PM 2Ring
3
Si bien no menciona específicamente el glob nulo, esta respuesta proporciona algunos antecedentes sobre el globing: unix.stackexchange.com/a/136409/22724
StrongBad
44
Tengo la impresión de que algunos piensan que debería ser obvio que nullglob debería estar habilitado por defecto. No creo que sea obvio. Que las expansiones ocurran en el shell antes de la invocación del comando significa que expandirse a nada es un comportamiento menos intuitivo que el globo que permanece sin cambios.
kojiro
2
@kojiro ¿Menos intuitivo para quién? Cualquiera que esté familiarizado con los shells * NIX sabe que *es un problema y se expande a todos los archivos existentes ; ¿Cómo es "intuitivo" que haya un caso especial en el que los globos de directorio vacíos se "expandan" a un literal *?
Kyle Strand

Respuestas:

78

La nullglobopción (que por cierto es una zshinvención, solo añadida años después a bash( 2.0)) no sería ideal en varios casos. Y lses un buen ejemplo:

ls *.txt

O su equivalente más correcto:

ls -- *.txt

Con nullglobon se ejecutaría lssin ningún argumento que se trata como ls -- .(enumere el directorio actual) si no coinciden los archivos, lo que probablemente sea peor que llamar lscon un literal *.txtcomo argumento.

Tendría problemas similares con la mayoría de las utilidades de texto:

grep foo *.txt

Buscaría foola entrada estándar si no hay txtarchivo.

Un valor predeterminado más sensato, y el de csh, tcsh, zsh o fish 2.3+ (y de los primeros shells de Unix) es cancelar el comando por completo si el glob no coincide.

bash(desde la versión 3) tiene una failglobopción para eso (interesante para esta discusión, ya que al contrario de ashAT&T ksho zsh, bashno admite ámbitos locales para las opciones (aunque eso cambiará en 4.4), esa opción cuando está habilitada globalmente rompe algunas cosas como las funciones bash-complete).

Tenga en cuenta que csh y tcsh son ligeramente diferentes de zsh, fisho bash -O failgloben casos como:

ls -- *.txt *.html

Donde necesita que todos los globos no coincidan para que se cancele el comando. Por ejemplo, si hay un archivo txt y ningún archivo html, se convierte en:

ls -- file.txt

Usted puede conseguir que el comportamiento con zshla setopt cshnullglobaunque de una manera más sensata de hacerlo en zshsería utilizar un pegote como:

ls -- *.(txt|html)

En zshy ksh93, también puede aplicar nullglob en función de cada globo, que es un enfoque mucho más sensato que modificar una configuración global:

files=(*.txt(N))  # zsh
files=(~(N)*.txt) # ksh93

crearía una matriz vacía si no hay un txtarchivo en lugar de fallar el comando con un error (o convertirlo en una matriz con un *.txtargumento literal con otros shells).

Las versiones fishanteriores a la 2.3 funcionarían de la misma manera bash -O nullglobpero darían una advertencia cuando sean interactivas cuando un globo no tenga coincidencia. Desde 2.3, funciona como zshexcepto para los globos utilizados en for, seto count.

Ahora, en la nota del historial, el comportamiento fue realmente roto por el shell Bourne. En versiones anteriores de Unix, el bloqueo se realizaba a través del /etc/globayudante y ese ayudante se comportaba como csh: fallaría el comando si ninguno de los globos coincidía con ningún archivo y, de lo contrario, eliminaría los globos sin coincidencia.

Entonces, la situación en la que estamos hoy se debe a una mala decisión tomada en el shell Bourne.

Tenga en cuenta que el shell Bourne (y el shell C) vienen con otra nueva característica de Unix: el entorno. Eso significa la expansión de variables (su predecesor sólo tenía los $1, $2... parámetros posicionales). El shell Bourne también introdujo la sustitución de comandos.

Otra mala decisión de diseño del shell Bourne fue realizar el bloqueo (y la división) tras la expansión de las variables y la sustitución de comandos (posiblemente por compatibilidad con el shell Thompson, donde echo $1aún se invocaría /etc/globsi $1contuviera comodines (era más como una expansión de macro preprocesador). allí, como en el valor expandido, se analizó nuevamente como código shell)).

Los globos fallidos que no coinciden significarían, por ejemplo, que:

pattern='a.*b'
grep $pattern file

fallaría el comando (a menos que haya algunos a.whateverbarchivos en el directorio actual). csh(que también realiza el bloqueo en la expansión variable) falla el comando en ese caso (y diría que es mejor que dejar un error latente allí, incluso si no es tan bueno como no hacerlo en absoluto zsh).

Stéphane Chazelas
fuente
Hay otro problema de usabilidad: nullglobparece romper la finalización de tabulación (presionar la tecla tab no hace nada cuando está habilitado).
Kyle Strand
1
Es posible utilizar nullglob en una por glob base con la fiesta - aunque la sintaxis no es tan elegante como zshfiles=$(shopt -s nullglob;echo *.txt)
Jon Nalley
2
@JonNalley, que almacena la concatenación (con espacio) de los nombres de archivo (con posible transformación con xpg_echo) en variables escalares . Necesitaría algo como readarray -td '' files < <(shopt -s nullglob; printf '%s\0' *.txt)con bash4.4 o superior o (shopt -s nullglob; printf '%s\0' *.txt) | xargs -r0 cmdcon GNU xargspara que sea utilizable con nombres de archivo arbitrarios. O, aún con bash4.4, use una función auxiliar que use local -(copiada de ceniza 25 años después) para un alcance local de opciones.
Stéphane Chazelas