Comment utiliser le script bash pour lire le contenu d'un fichier binaire?

15

Je veux lire un caractère, puis une longueur de chaîne fixe (la chaîne n'est pas terminée par null dans le fichier, et sa longueur est donnée par le caractère précédent).

Comment puis-je faire cela dans un script bash? Comment définir la variable chaîne pour que je puisse faire un post-traitement dessus?

Amanda
la source

Réponses:

19

Si vous souhaitez vous en tenir aux utilitaires shell, vous pouvez utiliser headpour extraire un certain nombre d'octets et odconvertir un octet en nombre.

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)

Cependant, cela ne fonctionne pas pour les données binaires. Il y a deux problèmes:

  • Substitution de commande $(…)supprime les sauts de lignes finales dans la sortie de commande. Il existe une solution assez simple: assurez-vous que la sortie se termine par un caractère autre qu'une nouvelle ligne, puis supprimez ce caractère.

    string=$(head -c $n; echo .); string=${string%.}
  • Bash, comme la plupart des shells, ne gère pas les octets nuls . Depuis bash 4.1, les octets nuls sont simplement supprimés du résultat de la substitution de commande. Dash 0.5.5 et pdksh 5.2 ont le même comportement et ATT ksh arrête la lecture au premier octet nul. En général, les shells et leurs utilitaires ne sont pas conçus pour traiter des fichiers binaires. (Zsh est l'exception, il est conçu pour prendre en charge les octets nuls.)

Si vous avez des données binaires, vous voudrez passer à un langage comme 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
'
Gilles 'SO- arrête d'être méchant'
la source
Les scripts shell +1 ne sont pas toujours appropriés
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
la source
5
read -Ns'arrête à des octets nuls, donc ce n'est pas une façon appropriée de travailler avec des données binaires. En général, les shells autres que zsh ne peuvent pas gérer les valeurs nulles.
Gilles 'SO- arrête d'être méchant'
2

Si vous voulez pouvoir traiter des fichiers binaires en shell, la meilleure option (seulement?) Est de travailler avec l' outil hexdump .

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

Lecture seule X octets:

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

Lire la longueur (et travailler avec 0 comme longueur) puis "chaîne" comme valeur décimale d'octet:

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
la source
Plutôt que de simplement présenter un tas de commandes, pouvez-vous expliquer ce qu'elles font et comment elles fonctionnent? Que signifient les options? Quelle sortie l'utilisateur peut-il attendre de vos commandes? Veuillez ne pas répondre dans les commentaires; modifiez  votre réponse pour la rendre plus claire et plus complète.
G-Man dit `` Réintègre Monica '' le
2
Eh bien, je peux copier des pages de manuel ici, mais je ne vois pas l'intérêt. Il n'y a que des commandes de base utilisées ici, la seule astuce est l'utilisation de hexdump.
Clément Moulin - SimpleRezo
2
Downvoting parce que vous n'aimez pas / ne comprenez pas ma réponse, sérieusement?
Clément Moulin - SimpleRezo
1

MISE À JOUR (avec du recul): ... Cette question / réponse (ma réponse) me fait penser au chien qui continue de courir après la voiture .. Un jour, enfin, il rattrape la voiture .. D'accord, il l'a attrapée, mais il ne peut vraiment pas faire grand-chose avec lui ... Cet anser "attrape" les cordes, mais alors vous ne pouvez pas faire grand-chose avec eux, s'ils ont intégré des octets nuls ... (donc un gros +1 à Gilles répond .. une autre langue peut être en règle ici.)

ddlit toutes les données ... Il ne rechignera certainement pas à zéro comme une "longueur" ... mais si vous avez \ x00 n'importe où dans vos données, vous devrez être créatif sur la façon dont vous les manipulez; ddn'a aucun problème avec cela, mais votre script shell aura des problèmes (mais cela dépend de ce que vous voulez faire avec les données) ... Ce qui suit renvoie essentiellement chaque "chaîne de données", dans un fichier avec un séparateur de ligne entre chaque strin ...

btw: Vous dites "caractère", et je suppose que vous voulez dire "octet" ...
mais le mot "caractère" est devenu ambigu de nos jours d'UNICODE, où seul le jeu de caractères ASCII 7 bits utilise un seul octet par caractère ... Et même au sein du système Unicode, le nombre d'octets varie en fonction de la méthode de codage des caractères , par exemple. UTF-8, UTF-16, etc.

Voici un script simple pour mettre en évidence la différence entre un "caractère" texte et des octets.

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 votre caractère de longueur est long de 1 octet et indique une longueur d'octet , alors ce script devrait faire l'affaire, même si les données contiennent des caractères Unicode ... ddne voit que les octets quel que soit le paramètre local ...

Ce script utilise ddpour lire le fichier binaire et génère les chaînes séparées par un séparateur "====" ... Voir le script suivant pour les données de 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

sortie

Ce script crée des données de test qui incluent un préfixe de 3 octets par ligne ...
Le préfixe est un seul caractère Unicode codé 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
la source
1
Votre code semble plus compliqué qu'il ne devrait l'être, en particulier le générateur de données de test aléatoire. Vous pouvez obtenir des octets aléatoires /dev/urandomsur la plupart des unités. Et les données de test aléatoires ne sont pas les meilleures données de test, vous devez vous assurer de traiter les cas difficiles tels que, ici, les caractères nuls et les retours à la ligne aux endroits limites.
Gilles 'SO- arrête d'être méchant'
Oui merci. J'ai pensé à utiliser / dev / random mais j'ai pensé que la génération de données de test n'était pas très importante, et je voulais tester drive 'numrandom' (que vous avez mentionné ailleurs; 'num-utils'some nice features.). Je viens de regarder de plus près votre réponse, et j'ai réalisé que vous faites à peu près la même chose, sauf qu'elle est plus succincte :) .. Je n'avais pas remarqué que vous aviez énoncé les points clés en 3 lignes! Je m'étais concentré sur vos références dans d' autres langues . Le faire fonctionner était une bonne expérience et je comprends maintenant mieux vos références aux autres langues! \ x00 peut être un shell-stopper
Peter.O
0

Celui-ci suffit de copier un fichier binaire:

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