¿Por qué $ '\ 0' es lo mismo que ''?

10

Una forma común de hacer cosas con un par de archivos es, y no me pegue por eso:

for f in $(ls); do 

Ahora, para estar a salvo de archivos con espacios u otros caracteres extraños, una forma ingenua sería hacer:

find . -type f -print0 | while IFS= read -r -d '' file; 

Aquí, la -d ''abreviatura es para configurar el ASCII NUL como en -d $'\0'.

¿Pero por qué es así? ¿Por qué son ''y $'\0'lo mismo? ¿Se debe a que las raíces C de Bash con una cadena vacía siempre están terminadas en nulo?

slhck
fuente
Refiriéndose a la forma "ingenua", ¿hay una mejor manera de hacerlo?
iruvar
2
Por cierto, si desea realizar operaciones seguras iterando sobre un conjunto de archivos, use en for f in *lugar de analizar ls.
@htor Sé que for i in $(ls)es terriblemente estúpido, estoy casi avergonzado de haberlo usado como un mal ejemplo aquí.
slhck
@ChandraRavoori Sí, por ejemplo, usando en find … -execlugar de recorrer los archivos, lo que funciona para la mayoría de los casos en los que usaría un bucle for. Aquí, findse encarga de todo por ti.
slhck
@slhck, gracias. ¿Qué pasa con las situaciones que involucran operaciones de varios pasos en cada archivo donde un bucle puede ser preferible por razones de legibilidad? ¿Existe una mejor opción de bucle que la "manera ingenua" anterior?
iruvar

Respuestas:

10

Las man page of bashlecturas:

          -d delim
                 The first character of delim is  used  to  terminate  the
                 input line, rather than newline.

Debido a que las cadenas generalmente terminan en nulo, el primer carácter de una cadena vacía es el byte nulo. - Tiene sentido para mi. :)

La fuente lee:

static unsigned char delim;
[...]
    case 'd':
      delim = *list_optarg;
      break;

Para una cadena vacía delimes simplemente el byte nulo.

michas
fuente
Cuando dice "las cadenas suelen tener terminación nula", ¿ no es ese el caso en algún lugar de un entorno POSIX? Desde los días en que estaba aprendiendo C para la escuela, por supuesto, tiene sentido asumirlo; Solo estaba revisando.
slhck
Pero se podría considerar que cualquier cadena contiene arbitrariamente muchas cadenas vacías, por ejemplo, si concatena '' y "X" se obtiene "X". Entonces, podría argumentar que el primer encuentro de bash de subcadena es la cadena vacía. Por ejemplo, si usa la cadena vacía en JavaScript, split()se dividirá entre cada carácter. Sospecho que "por razones históricas" puede ser la mejor explicación que podamos obtener.
donothings exitosamente
Bueno, no tanto porque "concatenación" un estilo de C '\0'con 'X\0'debe darle 'X\0', si el derecho de hacer. Esto no tiene mucho que ver con funciones de alto nivel en lenguajes como JavaScript @don
slhck
Gracias, michas, por agregar la fuente. delim = *list_optarg;deja en claro por qué es así.
slhck
@slhck: Lo siento, no me puse en claro. Usted preguntó "¿por qué ''y $'\0'lo mismo?", Michas dio la explicación inmediata de "eso es lo que hace el código". Esbocé una forma alternativa de manejar la cadena vacía que vi como igualmente razonable y sugerí que elegir una u otra era simplemente una cuestión de convención o casualidad.
donothings exitosamente
6

Hay dos deficiencias en bash que se compensan entre sí.

Cuando escribe $'\0', eso se trata internamente de manera idéntica a la cadena vacía. Por ejemplo:

$ a=$'\0'; echo ${#a}
0

Esto se debe a que internamente bash almacena todas las cadenas como cadenas C , que tienen terminación nula: un byte nulo marca el final de la cadena. Bash trunca silenciosamente la cadena al primer byte nulo (¡que no es parte de la cadena!).

# a=$'foo\0bar'; echo "$a"; echo ${#a}
foo
3

Cuando pasa una cadena como argumento a la -dopción de la readconstrucción, bash solo mira el primer byte de la cadena. Pero en realidad no verifica que la cadena no esté vacía. Internamente, una cadena vacía se representa como una matriz de bytes de 1 elemento que contiene solo un byte nulo. Entonces, en lugar de leer el primer byte de la cadena, bash lee este byte nulo.

Luego, internamente, la maquinaria detrás de la construcción readfunciona bien con bytes nulos; sigue leyendo byte a byte hasta que encuentra el delimitador.

Otras conchas se comportan de manera diferente. Por ejemplo, ash y ksh ignoran los bytes nulos cuando leen la entrada. Con ksh, ksh -d ""lee hasta una nueva línea. Los shells están diseñados para hacer frente bien al texto, no a los datos binarios. Zsh es una excepción: utiliza una representación de cadena que hace frente a bytes arbitrarios, incluidos los bytes nulos; en zsh, $'\0'es una cadena de longitud 1 (pero read -d '', curiosamente, se comporta como read -d $'\0').

Gilles 'SO- deja de ser malvado'
fuente
El comportamiento de readcambiado en bash 4.3 para que ahora omita bytes nulos. Por ejemplo, read x< <(printf a\\0a)establece xen aalugar de a.
Lri