¿Cómo usar el script bash para leer el contenido del archivo binario?

15

Quiero leer un carácter y luego una longitud fija de cadena (la cadena no termina en nulo en el archivo, y su longitud viene dada por el carácter anterior).

¿Cómo puedo hacer esto en un script bash? ¿Cómo definir la variable de cadena para que pueda realizar un procesamiento posterior en ella?

Amanda
fuente

Respuestas:

19

Si desea seguir con las utilidades de shell, puede utilizar headpara extraer una cantidad de bytes y odconvertir un byte en un número.

export LC_ALL=C    # make sure we aren't in a multibyte locale
n=$(head -c 1 | od -An -t u1)
string=$(head -c $n)

Sin embargo, esto no funciona para datos binarios. Hay dos problemas:

  • La sustitución de comandos $(…)tiras nuevas líneas finales en la salida del comando. Hay una solución bastante fácil: asegúrese de que la salida termine en un carácter que no sea una nueva línea, luego elimine ese carácter.

    string=$(head -c $n; echo .); string=${string%.}
  • Bash, como la mayoría de los shells, es malo para tratar con bytes nulos . A partir de bash 4.1, los bytes nulos simplemente se eliminan del resultado de la sustitución del comando. Dash 0.5.5 y pdksh 5.2 tienen el mismo comportamiento, y ATT ksh deja de leer en el primer byte nulo. En general, los shells y sus utilidades no están orientados a tratar con archivos binarios. (Zsh es la excepción, está diseñado para admitir bytes nulos).

Si tiene datos binarios, querrá cambiar a un idioma como Perl o Python.

<input_file perl -e '
  read STDIN, $c, 1 or die $!;    # read length byte
  $n = read STDIN, $s, ord($c);   # read data
  die $! if !defined $n;
  die "Input file too short" if ($n != ord($c));
  # Process $s here
'
<input_file python -c '
  import sys
  n = ord(sys.stdin.read(1))      # read length byte
  s = sys.stdin.read(n)           # read data
  if len(s) < n: raise ValueError("input file too short")
  # Process s here
'
Gilles 'SO- deja de ser malvado'
fuente
Los scripts de shell de +1 no siempre son apropiados
forcefsck
2
exec 3<binary.file     # open the file for reading on file descriptor 3
IFS=                   #
read -N1 -u3 char      # read 1 character into variable "char"

# to obtain the ordinal value of the char "char"
num=$(printf %s "$char" | od -An -vtu1 | sed 's/^[[:space:]]*//')

read -N$num -u3 str    # read "num" chars
exec 3<&-              # close fd 3
Glenn Jackman
fuente
55
read -Nse detiene en bytes nulos, por lo que esta no es una forma adecuada de trabajar con datos binarios. En general, los shells que no sean zsh no pueden hacer frente a los valores nulos.
Gilles 'SO- deja de ser malvado'
2

Si desea poder manejar archivos binarios en shell, la mejor opción (¿solo?) Es trabajar con la herramienta hexdump .

hexdump -v -e '/1 "%u\n"' binary.file | while read c; do
  echo $c
done

Solo lectura X bytes:

head -cX binary.file | hexdump -v -e '/1 "%u\n"' | while read c; do
  echo $c
done

Lea la longitud (y trabaje con 0 como longitud) y luego "cadena" como valor decimal de byte:

len=$(head -c1 binary.file | hexdump -v -e '/1 "%u\n"')
if [ $len -gt 0 ]; then
  tail -c+2 binary.file | head -c$len | hexdump -v -e '/1 "%u\n"' | while read c; do
    echo $c
  done
fi
Clément Moulin - SimpleRezo
fuente
En lugar de solo presentar un montón de comandos, ¿puede explicar qué hacen y cómo funcionan? ¿Qué significan las opciones? ¿Qué salida puede esperar el usuario de sus comandos? Por favor no responda en los comentarios; edite  su respuesta para que sea más clara y completa.
G-Man dice 'reinstalar a Monica' el
2
Bueno, puedo copiar páginas de manual aquí, pero no veo el punto. Aquí solo se utilizan comandos básicos, el único truco es el uso de hexdump.
Clément Moulin - SimpleRezo
2
Voto porque no te gusta / entiendo mi respuesta, ¿en serio?
Clément Moulin - SimpleRezo
1

ACTUALIZACIÓN (en retrospectiva): ... Esta pregunta / respuesta (mi respuesta) me hace pensar en el perro que sigue persiguiendo el auto ... Un día, finalmente, se pone al día con el auto ... De acuerdo, lo atrapó, pero realmente no puede hacer mucho con eso ... Este anser 'atrapa' las cadenas, pero luego no puedes hacer mucho con ellas, si tienen bytes nulos incrustados ... (entonces un gran +1 a la respuesta de Gilles .. otro idioma puede estar en orden aquí.)

ddlee todos y cada uno de los datos ... Ciertamente no se reducirá a cero como una "longitud" ... pero si tiene \ x00 en cualquier parte de sus datos, deberá ser creativo sobre cómo manejarlo; ddno tiene problemas con él, pero su script de shell tendrá problemas (pero depende de lo que quiera hacer con los datos) ... Lo siguiente básicamente genera cada "cadena de datos", en un archivo con un divisor de línea entre cada cadena ...

por cierto: dices "carácter", y supongo que quieres decir "byte" ...
pero la palabra "carácter" se ha vuelto ambigua en estos días de UNICODE, donde solo el conjunto de caracteres ASCII de 7 bits usa un solo byte por carácter ... E incluso dentro del sistema Unicode, los recuentos de bytes varían según el método de codificación de caracteres , por ejemplo. UTF-8, UTF-16, etc.

Aquí hay un script simple para resaltar la diferencia entre un "carácter" de texto y bytes.

STRING="௵"  
echo "CHAR count is: ${#STRING}"  
echo "BYTE count is: $(echo -n $STRING|wc -c)" 
# CHAR count is: 1
# BYTE count is: 3  # UTF-8 ecnoded (on my system)

Si su carácter de longitud es de 1 byte e indica una longitud de byte , entonces este script debería hacer el truco, incluso si los datos contienen caracteres Unicode ... ddsolo ve bytes independientemente de la configuración regional ...

Este script se utiliza ddpara leer el archivo binario y genera las cadenas separadas por un divisor "====" ... Consulte el siguiente script para ver los datos de prueba

#   
div="================================="; echo $div
((skip=0)) # read bytes at this offset
while ( true ) ; do
  # Get the "length" byte
  ((count=1)) # count of bytes to read
  dd if=binfile bs=1 skip=$skip count=$count of=datalen 2>/dev/null
  (( $(<datalen wc -c) != count )) && { echo "INFO: End-Of-File" ; break ; }
  strlen=$((0x$(<datalen xxd -ps)))  # xxd is shipped as part of the 'vim-common' package
  #
  # Get the string
  ((count=strlen)) # count of bytes to read
  ((skip+=1))      # read bytes from and including this offset
  dd if=binfile bs=1 skip=$skip count=$count of=dataline 2>/dev/null
  ddgetct=$(<dataline wc -c)
  (( ddgetct != count )) && { echo "ERROR: Line data length ($ddgetct) is not as expected ($count) at offset ($skip)." ; break ; }
  echo -e "\n$div" >>dataline # add a newline for TEST PURPOSES ONLY...
  cat dataline
  #
  ((skip=skip+count))  # read bytes from and including this offset
done
#   
echo

salida

Este script crea datos de prueba que incluyen un prefijo de 3 bytes por línea ...
El prefijo es un único carácter Unicode codificado en UTF-8 ...

# build test data
# ===============
  prefix="௵"   # prefix all non-zero length strings will this obvious 3-byte marker.
  prelen=$(echo -n $prefix|wc -c)
  printf \\0 > binfile  # force 1st string to be zero-length (to check zero-length logic) 
  ( lmax=3 # line max ... the last on is set to  255-length (to check  max-length logic)
    for ((i=1;i<=$lmax;i++)) ; do    # add prefixed random length lines 
      suflen=$(numrandom /0..$((255-prelen))/)  # random length string (min of 3 bytes)
      ((i==lmax)) && ((suflen=255-prelen))      # make last line full length (255) 
      strlen=$((prelen+suflen))
      printf \\$((($strlen/64)*100+$strlen%64/8*10+$strlen%8))"$prefix"
      for ((j=0;j<suflen;j++)) ; do
        byteval=$(numrandom /9,10,32..126/)  # output only printabls ASCII characters
        printf \\$((($byteval/64)*100+$byteval%64/8*10+$byteval%8))
      done
        # 'numrandom' is from package 'num-utils"
    done
  ) >>binfile
#
Peter.O
fuente
1
Su código parece más complicado de lo que debería ser, especialmente el generador de datos de prueba aleatorio. Puede obtener bytes aleatorios /dev/urandomen la mayoría de los unices. Y los datos de prueba aleatorios no son los mejores datos de prueba, debe asegurarse de abordar casos difíciles como, aquí, caracteres nulos y nueva línea en lugares de límite.
Gilles 'SO- deja de ser malvado'
Si gracias. Pensé en usar / dev / random, pero pensé que la generación de datos de prueba no era de gran importancia, y quería probar la prueba 'numrandom' (que mencionaste en otra parte; 'num-utils's algunas características agradables). Acabo de examinar más de cerca su respuesta y me di cuenta de que está haciendo más o menos lo mismo, excepto que es más sucinta :) ¡No me había dado cuenta de que había indicado los puntos clave en 3 líneas! Me había centrado en sus referencias en otros idiomas . Llevarlo al trabajo fue una buena experiencia, ¡y ahora entiendo mejor sus referencias a otros idiomas! \ x00 puede ser un obturador
Peter.O
0

Este solo copia un archivo binario:

 while read -n 1 byte ; do printf "%b" "$byte" ; done < "$input" > "$output"
rzr
fuente