Wie benutze ich ein Bash-Skript, um den Inhalt von Binärdateien zu lesen?

14

Ich möchte ein Zeichen und dann eine feste Länge der Zeichenfolge lesen (die Zeichenfolge ist in der Datei nicht mit Null abgeschlossen, und ihre Länge wird durch das vorangegangene Zeichen angegeben).

Wie kann ich das in einem Bash-Skript machen? Wie definiere ich die String-Variable, damit ich sie nachbearbeiten kann?

Amanda
quelle

Antworten:

19

Wenn Sie bei Shell-Dienstprogrammen bleiben möchten, können Sie headeine Anzahl von Bytes extrahieren und odein Byte in eine Anzahl konvertieren.

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)

Dies funktioniert jedoch nicht für Binärdaten. Es gibt zwei Probleme:

  • Befehlssubstitutions $(…)strips final newlines in der Befehlsausgabe. Es gibt eine relativ einfache Problemumgehung: Stellen Sie sicher, dass die Ausgabe mit einem anderen Zeichen als einer neuen Zeile endet, und entfernen Sie dann dieses eine Zeichen.

    string=$(head -c $n; echo .); string=${string%.}
  • Bash ist, wie die meisten Shells, schlecht im Umgang mit Null-Bytes . Ab Bash 4.1 werden null Bytes einfach aus dem Ergebnis der Befehlssubstitution entfernt. Dash 0.5.5 und pdksh 5.2 haben dasselbe Verhalten, und ATT ksh stoppt das Lesen beim ersten Null-Byte. Im Allgemeinen sind Shells und ihre Dienstprogramme nicht auf den Umgang mit Binärdateien ausgerichtet. (Zsh ist die Ausnahme, es unterstützt null Bytes.)

Wenn Sie über Binärdaten verfügen, möchten Sie zu einer Sprache wie Perl oder Python wechseln.

<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 - hör auf böse zu sein'
quelle
+1 Shell-Skripte sind nicht immer angemessen
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
quelle
5
read -Nstoppt bei null Bytes, daher ist dies kein geeigneter Weg, um mit Binärdaten zu arbeiten. Im Allgemeinen können andere Shells als zsh nicht mit Nullen umgehen.
Gilles 'SO- hör auf böse zu sein'
2

Wenn Sie in der Lage sein möchten, mit Binärdateien in der Shell umzugehen, ist die beste Option (nur?), Mit dem Hexdump- Tool zu arbeiten.

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

Nur X Bytes lesen:

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

Lese die Länge (und arbeite mit 0 als Länge) und dann "string" als Byte-Dezimalwert:

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
quelle
Können Sie erklären, was sie tun und wie sie funktionieren, anstatt nur eine Reihe von Befehlen zu präsentieren? Was bedeuten die Optionen? Welche Ausgabe kann der Benutzer von Ihren Befehlen erwarten? Bitte antworten Sie nicht in Kommentaren; Bearbeiten Sie  Ihre Antwort, um sie klarer und vollständiger zu gestalten.
G-Man sagt, dass Monica
2
Ich kann hier Manpages kopieren, aber ich verstehe den Punkt nicht. Hier werden nur grundlegende Befehle verwendet, der einzige Trick ist die Verwendung von hexdump.
Clément Moulin - SimpleRezo
2
Downvoting, weil du meine Antwort nicht magst / verstehst, im Ernst?
Clément Moulin - SimpleRezo
1

UPDATE (im Nachhinein): ... Diese Frage / Antwort (meine Antwort) lässt mich an den Hund denken, der das Auto immer wieder verfolgt. Eines Tages holt er schließlich das Auto ein. Okay, er hat es verstanden, aber er kann wirklich nicht viel damit anfangen ... Dieser Anser "fängt" die Saiten, aber dann kann man nicht viel damit anfangen, wenn sie Null-Bytes eingebettet haben ... (also eine große +1 an Gilles Antwort .. hier ist möglicherweise eine andere Sprache in Ordnung.)

ddLiest alle Daten ... Es wird mit Sicherheit nicht bei Null als "Länge" aufgehen ... aber wenn Sie \ x00 irgendwo in Ihren Daten haben, müssen Sie kreativ sein, wie Sie damit umgehen;ddhat keine propblems damit, aber Ihr Shell-Skript wird Probleme haben (aber es hängt davon ab, was Sie mit den Daten tun möchten) ... Im Folgenden wird grundsätzlich jeder "Datenstring" in eine Datei mit einem Zeilenteiler zwischen den einzelnen Strins ausgegeben ...

btw: Sie sagen "Zeichen", und ich nehme an, Sie meinen "Byte" ...
aber das Wort "Zeichen" ist in diesen Tagen von UNICODE, in denen nur der 7-Bit-ASCII-Zeichensatz ein einzelnes Byte pro Zeichen verwendet, mehrdeutig geworden ... Und auch innerhalb des Unicode - System Bytezählwerte auf dem Verfahren variieren in Abhängigkeit der Codierung Zeichen , z. B. UTF-8, UTF-16 usw.

Hier ist ein einfaches Skript, um den Unterschied zwischen einem Text- "Zeichen" und Bytes hervorzuheben.

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)

Wenn Ihre Länge Zeichen 1 Byte lang und weist auf eine Byte-Länge , dann sollte dieses Skript den Trick tun, auch wenn die Daten Unicode - Zeichen enthält ... ddsieht nur Bytes unabhängig von einer Lokalisierungs - Einstellungen ...

Dieses Skript ddliest die Binärdatei und gibt die durch einen "====" - Teiler getrennten Zeichenfolgen aus. Testdaten finden Sie im nächsten Skript

#   
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

Ausfahrt

Dieses Skript erstellt Testdaten, die ein 3-Byte-Präfix pro Zeile enthalten ...
Das Präfix ist ein einzelnes UTF-8-codiertes Unicode-Zeichen ...

# 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
quelle
1
Ihr Code sieht komplizierter aus, als er sein sollte, insbesondere der Zufallstestdatengenerator. Sie können zufällige Bytes von den /dev/urandommeisten Unices erhalten. Und zufällige Testdaten sind nicht die besten Testdaten. Sie sollten darauf achten, schwierige Fälle wie hier Nullzeichen und Zeilenvorschub an Grenzstellen zu behandeln.
Gilles 'SO- hör auf böse zu sein'
Ja dank. Ich dachte an die Verwendung von / dev / random, stellte aber fest, dass die Testdaten von keiner großen Bedeutung sind, und ich wollte 'numrandom' testen (was Sie an anderer Stelle erwähnt haben; 'num-utils' einige nette Funktionen). Ich habe mir Ihre Antwort nur genauer angesehen und festgestellt, dass Sie so ziemlich dasselbe tun, nur dass es prägnanter ist :) .. Ich hatte nicht bemerkt, dass Sie die wichtigsten Punkte in drei Zeilen angegeben hatten! Ich habe mich auf Ihre Referenzen in anderen Sprachen konzentriert . Es war eine gute Erfahrung, sie zum Laufen zu bringen, und jetzt verstehe ich Ihre Referenzen in anderen Sprachen besser! \ x00 kann ein Shell-Stopper sein
Peter.O
0

Dieser kopiert einfach eine Binärdatei:

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