Asigna cadena que contiene carácter nulo (\ 0) a una variable en Bash

Al tratar de procesar una lista de nombres de archivo / carpeta correctamente ( ver mis otras preguntas ) mediante el uso de un carácter NULL como delimitador tropecé con un comportamiento extraño de Bash que no entiendo:

Al asignar una cadena que contiene uno o más caracteres NULL a una variable, los caracteres NULL se pierden / ignoran / no se almacenan.

Por ejemplo,

echo -ne "n\0m\0k" | od -c # -> 0000000 n \0 m \0 k 

Pero:

 VAR1=`echo -ne "n\0m\0k"` echo -ne "$VAR1" | od -c # -> 0000000 nmk 

Esto significa que tendría que escribir esa cadena en un archivo (por ejemplo, en / tmp) y volver a leerla desde allí si no se desea o no es posible realizar la conexión directamente.

Al ejecutar estos scripts en Z shell (zsh) las cadenas que contienen \ 0 se conservan en ambos casos, pero lamentablemente no puedo suponer que zsh esté presente en los sistemas que ejecutan mi script mientras Bash debería estarlo.

¿Cómo se pueden almacenar o manejar las cadenas que contienen \ 0 caracteres sin perder ningún (meta) caracteres?

En Bash, no puede almacenar el carácter NULL en una variable.

Sin embargo, puede almacenar un volcado hexadecimal simple de los datos (y luego revertir esta operación nuevamente) utilizando el comando xxd .

 VAR1=`echo -ne "n\0m\0k" | xxd -p | tr -d '\n'` echo -ne "$VAR1" | xxd -r -p | od -c # -> 0000000 n \0 m \0 k 

Como otros ya han indicado, no puede almacenar / usar NUL char :

  • en una variable
  • en una discusión de la línea de comando.

Sin embargo, puede manejar cualquier información binaria (incluido el carácter NUL):

  • en tuberías
  • en archivos

Entonces para responder a su última pregunta:

¿Alguien puede darme una pista de cómo las cadenas que contienen \ 0 caracteres se pueden almacenar o manejar de manera eficiente sin perder ningún (meta) caracteres?

Puede usar archivos o tuberías para almacenar y manejar eficientemente cualquier cadena con cualquier metacaraculo.

Si planea manejar los datos, debe tener en cuenta que:

  • Solo el carácter NUL será comido por la variable y el argumento de la línea de comando, puedes verificar esto .
  • Tenga cuidado de que la sustitución de comando (como $(command..) o `command..` ) tiene un giro adicional por encima de ser una variable, ya que comerá sus nuevas líneas de finalización .

Pasando por alto las limitaciones

Si desea usar variables, entonces debe deshacerse del carácter NUL codificándolo, y varias otras soluciones aquí brindan formas inteligentes de hacerlo (una forma obvia es usar, por ejemplo, encoding / deencoding base64).

Si le preocupan la memoria o la velocidad, probablemente desee utilizar un analizador mínimo y solo citar el carácter NUL (y el carácter de cita). En este caso, esto te ayudaría a:

 quote() { sed 's/\\/\\\\/g;s/\x0/\\0/g'; } 

Luego, puede proteger sus datos antes de almacenarlos en variables y argumentos de línea de comandos conectando sus datos confidenciales a una quote , que generará una secuencia de datos segura sin caracteres NUL. Puede recuperar la cadena original (con caracteres NUL) utilizando echo -en "$var_quoted" que enviará la cadena correcta en la salida estándar.

Ejemplo:

 ## Our example output generator, with NUL chars ascii_table() { echo -en "$(echo '\'0{0..3}{0..7}{0..7} | tr -d " ")"; } ## store myvar_quoted=$(ascii_table | quote) ## use echo -en "$myvar_quoted" 

Nota: uso | hd | hd para obtener una vista limpia de sus datos en hexadecimal y comprobar que no haya perdido ningún carácter NUL.

Cambio de herramientas

Recuerde que puede ir bastante lejos con las tuberías sin usar variables ni argumentos en la línea de comandos, no olvide por ejemplo la construcción <(command ...) que creará una tubería con nombre (tipo de archivo temporal).

EDITAR: la primera implementación de la quote fue incorrecta y no se trataría correctamente con \ caracteres especiales interpretados por echo -en . Gracias @xhienne por detectar eso.

Utilice uuencode y uudecode para la portabilidad POSIX

xxd y base64 no son POSIX 7 sino uuencode .

 VAR="$(uuencode -m <(printf "a\0\n") /dev/stdout)" uudecode -o /dev/stdout <(printf "$VAR") | od -tx1 

Salida:

 0000000 61 00 0a 0000003 

Lamentablemente, no veo una alternativa POSIX 7 para la extensión de sustitución Bash process <() excepto escribir en un archivo, y no están instalados en Ubuntu 12.04 de forma predeterminada (paquete sharutils ).

Así que supongo que la verdadera respuesta es: no use Bash para esto, use Python o algún otro lenguaje interpretado por Saner.

Me encanta la respuesta de jeff . Usaría la encoding Base64 en lugar de xxd. Ahorra un poco de espacio y sería (creo) más reconocible en cuanto a lo que se pretende.

 VAR=$(echo -n "foo\0bar" | base64) echo -n $VAR | base64 -d | xargs -0 ... 

En cuanto a -e, no es necesario porque el intérprete de comandos ya interpreta el escape antes de que llegue a hacerse eco. También parece recordar algo acerca de que “echo-e” no es seguro si haces eco de cualquier entrada del usuario, ya que podrían inyectar secuencias de escape que echo interpretará y terminará con cosas malas.