Como usar o script bash para ler o conteúdo do arquivo binário?

12

Eu quero ler um caractere e, em seguida, um comprimento fixo de string (a string não é nula terminada no arquivo, e seu comprimento é dado pelo caractere precedente).

Como posso fazer isso em um script bash? Como definir a variável string para que eu possa fazer um pós-processamento?

    
por Amanda 07.04.2011 / 08:55

5 respostas

17

Se você quiser manter os utilitários de shell, use head para extrair vários bytes e od para converter um byte em um 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)

No entanto, este não funciona para dados binários. Existem dois problemas:

  • A substituição de comandos $(…) retira novas linhas finais na saída do comando. Há uma solução bastante fácil: certifique-se de que a saída termine em um caractere diferente de uma nova linha e, em seguida, tire esse caractere.

    string=$(head -c $n; echo .); string=${string%.}
    
  • Bash, como a maioria das shells, é ruim para lidar com bytes nulos . A partir do bash 4.1, os bytes nulos são simplesmente eliminados do resultado da substituição do comando. O Dash 0.5.5 e o pdksh 5.2 possuem o mesmo comportamento, e o ATT ksh para de ler no primeiro byte nulo. Em geral, shells e seus utilitários não são voltados para lidar com arquivos binários. (Zsh é a exceção, ele é projetado para suportar bytes nulos.)

Se você tiver dados binários, deseje alternar para um idioma como Perl ou 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
'
    
por 07.04.2011 / 09:29
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
    
por 07.04.2011 / 14:56
1

UPDATE (em retrospecto): ... Esta pergunta / resposta (minha resposta) me faz pensar no cachorro que fica perseguindo o carro .. Um dia, finalmente, ele alcança o carro .. Ok, ele pegou isso, mas ele realmente não pode fazer muito com isso ... Este anser 'pega' as cordas, mas então você não pode fazer muito com elas, se elas tiverem embutido bytes nulos ... (então um grande +1 para Gilles responder .. outra língua pode estar em ordem aqui.)

dd lê todo e qualquer dado ... Certamente não vai atrapalhar zero como "comprimento" ... mas se você tiver \ x00 em qualquer lugar em seus dados, precisa ser criativo como você lida com isso; dd não tem nenhum problema com isso, mas seu script de shell terá problemas (mas depende do que você deseja fazer com os dados) ... O seguinte basicamente gera cada "cadeia de dados", em um arquivo com um divisor de linha entre cada strin ...

btw: Você diz "personagem", e eu assumo que você quer dizer "byte" ...
mas a palavra "caractere" tornou-se ambígua nesses dias de UNICODE, onde apenas o conjunto de caracteres ASCII de 7 bits usa um único byte por caractere ... E mesmo dentro do sistema Unicode, a contagem de bytes varia dependendo do método de codificação caracteres , por exemplo. UTF-8, UTF-16, etc.

Aqui está um script simples para destacar a diferença entre um "caractere" de texto e 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)

Se o tamanho do caractere for 1 byte e indicar byte-length , esse script deverá funcionar, mesmo que os dados contenham caracteres Unicode. . dd só vê bytes independentemente de qualquer definição de localidade ...

Este script usa dd para ler o arquivo binário e gera as sequências separadas por um divisor "====" ... Veja o próximo script para dados de teste

#   
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

sair

Este script cria dados de teste que incluem um prefixo de 3 bytes por linha ...
O prefixo é um único caractere Unicode codificado em 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
#
    
por 09.04.2011 / 17:44
0

Este apenas copia um arquivo binário:

 while read -n 1 byte ; do printf "%b" "$byte" ; done < "$input" > "$output"
    
por 17.11.2014 / 22:53
-1

Se você quer ser capaz de lidar com um arquivo binário no shell, a melhor opção (apenas?) é trabalhar com a ferramenta hexdump .

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

Ler apenas X bytes:

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

Leia o comprimento (e trabalhe com 0 como comprimento) e depois "string" 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
    
por 15.09.2018 / 16:08