¿Cómo puedo manejar datos binarios sin procesar en una tubería bash?

15

Tengo una función bash que toma un archivo como parámetro, verifica que el archivo existe y luego escribe cualquier cosa que salga del stdin en el archivo. La solución ingenua funciona bien para el texto, pero estoy teniendo problemas con datos binarios arbitrarios.

echo -n '' >| "$file" #Truncate the file
while read lines
do  # Is there a better way to do this? I would like one...
    echo $lines >> "$file"
done
David Souther
fuente

Respuestas:

15

Su forma es agregar saltos de línea a cada cosa que escriba en el espacio de cualquier separador ( $IFS) que esté usando para dividir la lectura. En lugar de dividirlo en nuevas líneas, simplemente tome todo y páselo. Puede reducir todo el código anterior a esto:

 cat - > $file

No necesita el bit truncado, esto se truncará y escribirá toda la secuencia STDIN.

Editar: si está usando zsh, puede usarlo > $fileen lugar del gato. Está redirigiendo a un archivo y truncándolo, pero si hay algo colgando esperando que algo acepte STDIN, se leerá en ese punto. Creo que puedes hacer algo como esto con bash pero tendrías que configurar un modo especial.

Caleb
fuente
No pude hacer que el ejemplo de redireccionamiento stdin funcione, pero cambiando el ejemplo cat a> | (No tengo set de noclobber) funciona de maravilla. Gracias por alegrarme el día ^. ^
David Souther
+1 para la versión sin gato. Siempre evite los gatos inútiles;)
rozcietrzewiacz
@rozcietrzewiacz: Cierto, excepto que fue una ocurrencia tardía y me equivoqué. Esto podría no ser un uso inútil del gato. Lo único que puedes hacer es > $file. Esto solo funciona como lo primero que busca stdin en el script de shell principal. Básicamente, todo el código de David se puede reducir a un solo personaje, pero creo que cat -es más elegante y menos problemático porque se entiende a simple vista.
Caleb
A veces pongo cuatro o cinco cats juntos, solo para molestar a los fanáticos de UUOC
Michael Mrozek
@MichaelMrozek: A veces nombro mis archivos de datos catsolo para que las personas que insisten en usarlo necesariamente tengan que hacer gimnasia mental para leer el código. Las tuberías con nombre también son buenos objetivos.
Caleb
7

Para leer un archivo de texto literalmente, no use plain read, que procesa la salida de dos maneras:

  • readinterpreta \como un personaje de escape; use read -rpara apagar esto.
  • readse divide en palabras en caracteres en $IFS; establecer IFSen una cadena vacía para desactivar esto.

El idioma habitual para procesar un archivo de texto línea por línea es

while IFS= read -r line; do 

Para obtener una explicación de este idioma, vea ¿Por qué se while IFS= readusa con tanta frecuencia, en lugar de IFS=; while read..? .

Para escribir una cadena literalmente, no use simplemente plain echo, que procesa la cadena de dos maneras:

  • En algunos shells, los echoprocesos escapan de barra invertida. (En bash, depende de si la xpg_echoopción está configurada).
  • Algunas cadenas se tratan como opciones, por ejemplo, -no -e(el conjunto exacto depende del shell).

Una forma portátil de imprimir una cadena literalmente es con printf. (No hay mejor manera en bash, a menos que sepa que su entrada no parece una opción echo). Use el primer formulario para imprimir la cadena exacta y el segundo formulario si desea agregar una nueva línea.

printf %s "$line"
printf '%s\n' "$line"

Esto solo es adecuado para procesar texto , porque:

  • La mayoría de los proyectiles se ahogarán con caracteres nulos en la entrada.
  • Cuando haya leído la última línea, no tiene forma de saber si había una nueva línea al final o no. (Algunos shells más antiguos pueden tener mayores problemas si la entrada no termina con una nueva línea).

No puede procesar datos binarios en el shell, pero las versiones modernas de utilidades en la mayoría de los dispositivos pueden hacer frente a datos arbitrarios. Para pasar toda la entrada a la salida, use cat. Ir por una tangente echo -n ''es una forma complicada y no portátil de no hacer nada; echo -nsería igual de bueno (o no dependiendo del shell), y :es más simple y totalmente portátil.

: >| "$file"
cat >>"$file"

o, más simple,

cat >|"$file"

En un script, generalmente no necesita usarlo >|ya que noclobberestá desactivado de manera predeterminada.

Gilles 'SO- deja de ser malvado'
fuente
gracias por señalar xpg_echo, ese es realmente un problema que estaba teniendo en otro lugar en mi código y ni siquiera me di cuenta. Re noclobber, tengo la costumbre de encenderlo en mi bashrc.
David Souther
0

Esto hará exactamente lo que quieres:

( while read -r -d '' ; do
    printf %s'\0' "${REPLY}" ;
  done ;

  # When read hits EOF, it returns non-zero which exits the while loop.
  # That data still needs to be output:
  printf %s "${REPLY}"
) >> ${file}

Sin embargo, tenga en cuenta el uso de memoria. Esto lee la entrada de forma nula delimitada.

Si no hay bytes \0 nulos en la entrada, bash primero tendrá que leer todo el contenido de la entrada en la memoria y luego emitirlo.

Con respecto a su paso truncado:

echo -n '' >| "$file" #Truncate the file

un mucho más simple y equivalente es:

> ${file}   #Truncate the file
Marc Tamsky
fuente