¿Cómo uso bytes nulos en Bash?


33

He leído que, dado que las rutas de archivos en Bash pueden contener cualquier carácter, excepto el byte nulo (byte de valor cero $'\0'), es mejor usar el byte nulo como separador. Por ejemplo, si la salida de findse enviará a otro programa, se recomienda utilizar la -print0opción (para las versiones findque lo tengan).

Pero aunque algo como esto funciona bien (imprimir rutas de archivos separadas por nuevas líneas, no se preocupe, esto es solo una demostración, en realidad no lo estoy haciendo en scripts reales):

find -print0 \
  | while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done

algo como esto no funciona:

for file in * ; do echo -n "$file"$'\0' ; done \
  | while IFS= read -r -d $'\0' ; do echo "$REPLY" ; done

Cuando pruebo solo la forparte -loop, encuentro que solo imprime todos los nombres de archivo juntos, sin el byte nulo en el medio.

¿Por qué es esto? ¿Que esta pasando?

Respuestas:


43

Bash utiliza cadenas de estilo C internamente, que terminan en bytes nulos. Esto significa que una cadena Bash (como el valor de una variable o un argumento para un comando) nunca puede contener un byte nulo. Por ejemplo, este mini script:

foobar=$'foo\0bar'    # foobar='foo' + null byte + 'bar'
echo "${#foobar}"     # print length of $foobar

en realidad imprime 3, porque en $foobarrealidad es solo 'foo': barviene después del final de la cadena.

Del mismo modo, echo $'foo\0bar'solo imprime foo, porque echono sabe sobre la \0barpieza.

Como puede ver, la \0secuencia en realidad es muy engañosa en una $'...'cadena de estilo; parece un byte nulo dentro de la cadena, pero no termina funcionando de esa manera. En su primer ejemplo, su readcomando tiene -d $'\0'. ¡Esto funciona, pero solo porque -d ''también funciona! (Esa no es una característica explícitamente documentada de read, pero supongo que funciona por la misma razón: ''es la cadena vacía, por lo que su byte nulo de terminación aparece de inmediato. Está documentado como "El primer carácter de delim ", y supongo que incluso funciona si el "primer carácter" ha pasado el final de la cadena!)-d delim

Pero como sabe por su findejemplo, es posible que un comando imprima un byte nulo y que ese byte se canalice a otro comando que lo lea como entrada. Ninguna parte de eso se basa en almacenar un byte nulo en una cadena dentro de Bash . El único problema con su segundo ejemplo es que no podemos usarlo $'\0'en un argumento para un comando; echo "$file"$'\0'podría imprimir felizmente el byte nulo al final, si supiera que lo desea.

Entonces, en lugar de usar echo, puede usar printf, que admite los mismos tipos de secuencias de escape que $'...'las cadenas de estilo. De esa manera, puede imprimir un byte nulo sin tener que tener un byte nulo dentro de una cadena. Eso se vería así:

for file in * ; do printf '%s\0' "$file" ; done \
  | while IFS= read -r -d '' ; do echo "$REPLY" ; done

o simplemente esto:

printf '%s\0' * \
  | while IFS= read -r -d '' ; do echo "$REPLY" ; done

(Nota: en echorealidad también tiene una -ebandera que le permitiría procesar \0e imprimir un byte nulo; pero luego también trataría de procesar cualquier secuencia especial en su nombre de archivo. Por lo tanto, el printfenfoque es más robusto).


Por cierto, hay algunas conchas que no permiten nula bytes de cuerdas dentro. Su ejemplo funciona bien en Zsh, por ejemplo (asumiendo la configuración predeterminada). Sin embargo, independientemente de su shell, los sistemas operativos tipo Unix no proporcionan una forma de incluir bytes nulos dentro de los argumentos a los programas (ya que los argumentos del programa se pasan como cadenas de estilo C), por lo que siempre habrá algunas limitaciones. (Su ejemplo puede funcionar en Zsh solo porque echoes un shell integrado, por lo que Zsh puede invocarlo sin depender del soporte del sistema operativo para invocar otros programas. Si usó en command echolugar de echo, de modo que omitió el builtin y utilizó el echoprograma independiente en el $PATH, verías el mismo comportamiento en Zsh que en Bash).


2
¿Por qué IFS se establece en nada si -d ''ya significa delimitar \0? Encontré una explicación aquí: stackoverflow.com/questions/8677546/…
CMCDragonkai
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.