Come usare lo script bash per leggere il contenuto del file binario?


15

Voglio leggere un carattere e quindi una lunghezza fissa della stringa (la stringa non è terminata con null nel file e la sua lunghezza è data dal carattere precedente).

Come posso farlo in uno script bash? Come definire la variabile stringa in modo che io possa fare un po 'di post-elaborazione su di essa?

Risposte:


19

Se si desidera attenersi alle utilità della shell, è possibile utilizzare headper estrarre un numero di byte e odper convertire un byte in un numero.

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)

Tuttavia, questo non funziona per i dati binari. Ci sono due problemi:

  • La sostituzione del comando $(…)elimina le nuove righe finali nell'output del comando. C'è una soluzione abbastanza semplice: assicurati che l'output finisca con un carattere diverso da una nuova riga, quindi rimuovi quel carattere.

    string=$(head -c $n; echo .); string=${string%.}
  • Bash, come la maggior parte delle shell, non è in grado di gestire byte null . A partire da bash 4.1, i byte null vengono semplicemente eliminati dal risultato della sostituzione del comando. Dash 0.5.5 e pdksh 5.2 hanno lo stesso comportamento e ATT ksh interrompe la lettura al primo byte null. In generale, le shell e le loro utilità non sono orientate alla gestione di file binari. (Zsh è l'eccezione, è progettata per supportare byte null.)

Se disponi di dati binari, ti consigliamo di passare a una lingua come 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
'

Gli script di shell +1 non sono sempre appropriati
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

5
read -Nsi ferma a byte null, quindi questo non è un modo adatto per lavorare con dati binari. In generale, shell diverse da zsh non possono far fronte a valori null.
Gilles 'SO- smetti di essere malvagio' il

2

Se vuoi essere in grado di gestire file binari in shell, l'opzione migliore (solo?) È quella di lavorare con lo strumento hexdump .

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

Leggi solo X byte:

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

Leggi lunghezza (e lavora con 0 come lunghezza) e quindi "stringa" come valore decimale 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

Piuttosto che presentare un gruppo di comandi, puoi spiegare cosa fanno e come funzionano? Cosa significano le opzioni? Quale output può aspettarsi l'utente dai tuoi comandi? Si prega di non rispondere nei commenti; modifica la  tua risposta per renderla più chiara e completa.
G-Man dice 'Reinstate Monica' il

2
Bene, posso copiare le manpage qui, ma non vedo il punto. Qui ci sono solo i comandi di base, l'unico trucco è l'uso di hexdump.
Clément Moulin - SimpleRezo

2
Downvoting perché non ti piace / capisci la mia risposta, sul serio?
Clément Moulin - SimpleRezo,

1

AGGIORNAMENTO (con il senno di poi): ... Questa domanda / risposta (la mia risposta) mi fa pensare al cane che continua a inseguire la macchina .. Un giorno, finalmente, raggiunge l'auto .. Va bene, l'ha presa, ma non può davvero farci molto ... Questa risposta "cattura" le stringhe, ma poi non puoi farci molto, se hanno dei byte nulli incorporati ... (quindi un grande +1 a Gilles risponde .. un'altra lingua potrebbe essere in ordine qui.)

ddlegge tutti i dati ... Certamente non si sfoglia a zero come "lunghezza" ... ma se hai \ x00 in qualsiasi punto dei tuoi dati, dovrai essere creativo nel modo in cui li gestisci; ddnon ha alcun problema con esso, ma lo script della shell avrà problemi (ma dipende da cosa vuoi fare con i dati) ... Di seguito, in sostanza, ogni "stringa di dati" viene emessa, in un file con un divisore di linea tra ogni strin ...

btw: dici "carattere", e suppongo che intendi "byte" ...
ma la parola "carattere" è diventata ambigua in questi giorni di UNICODE, dove solo il set di caratteri ASCII a 7 bit utilizza un singolo byte per carattere ... E anche all'interno del sistema Unicode, il conteggio dei byte varia in base al metodo di codifica dei caratteri , ad es. UTF-8, UTF-16, ecc.

Ecco un semplice script per evidenziare la differenza tra un "carattere" di testo e byte.

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)

Se il tuo carattere di lunghezza è lungo 1 byte e indica una lunghezza di byte , questo script dovrebbe fare il trucco, anche se i dati contengono caratteri Unicode ... ddvede solo byte indipendentemente da qualsiasi impostazione locale ...

Questo script utilizza ddper leggere il file binario e genera le stringhe separate da un divisore "====" ... Vedi lo script successivo per i dati di test

#   
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

Uscita

Questo script crea dati di test che includono un prefisso di 3 byte per riga ...
Il prefisso è un singolo carattere Unicode codificato 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
#

1
Il tuo codice sembra più complicato di quanto dovrebbe essere, in particolare il generatore di dati di test casuali. Puoi ottenere byte casuali dalla /dev/urandommaggior parte degli unices. E i dati di test casuali non sono i migliori dati di test, dovresti assicurarti di affrontare casi difficili come, qui, caratteri null e newline in punti di confine.
Gilles 'SO- smetti di essere malvagio' il

Sì grazie. Ho pensato di usare / dev / random, ma ho pensato che la gen dei dati di test non fosse di grande importanza, e volevo testare il "numrandom" (se non altro che hai menzionato altrove; "num-utils's some nice features.). Ho appena dato un'occhiata più da vicino alla tua risposta e mi sono reso conto che stai facendo praticamente la stessa cosa, tranne per il fatto che è più conciso :) .. Non avevo notato che avevi dichiarato i punti chiave in 3 righe! Mi ero concentrato sui tuoi riferimenti in altre lingue . Farlo funzionare è stata una bella esperienza, e ora capisco meglio i tuoi riferimenti ad altre lingue! \ x00 può essere un tappo di conchiglia
Peter.O

0

Questo basta copiare un file binario:

 while read -n 1 byte ; do printf "%b" "$byte" ; done < "$input" > "$output"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.