Wie konvertiere ich in Bash 8 Bytes in ein vorzeichenloses Int (64-Bit-LE)?

8

Wie kann ich 8 Bytes als vorzeichenloses int (Little Endian) 'lesen / interpretieren' ?
Vielleicht gibt es dafür eine magische Bash-Fu-Konvertierung?

UPDATE:
Es scheint, dass bei der Interpretation meiner Frage etwas überkreuzt wurde. Hier ist ein breiteres Beispiel dafür, was ich versuche zu tun.

Ich möchte die ersten (und letzten) 64 KB einer Datei lesen. Jedes 8-Byte-Wort ist als vorzeichenlose 64-Bit-Little-Endian-Ganzzahl zu interpretieren. Diese Ganzzahlen sollen in einer Hashing-Berechnung verwendet werden, die die Datei eindeutig identifiziert. Es müssen also viele Berechnungen durchgeführt werden. ∴ Geschwindigkeit wird bevorzugt, ist aber nicht kritisch. (Warum mache ich das? Weil smplayerdie Namen der INI-Dateien für abgespielte Medien gehasht werden und ich auf diese Dateien zugreifen und sie ändern möchte, ahme ich den C ++ - Code des Smplayers in Bash nach.)

Eine Lösung, die das Akzeptieren einer Piped-Eingabe ermöglicht, wäre optimal und wahrscheinlich aufgrund der Art und Weise, wie Bash-Variablen \ x00 nicht verarbeiten können, von wesentlicher Bedeutung.

Mir ist klar, dass so etwas wahrscheinlich besser für Python, Perl und C / C ++ geeignet ist, aber ich kenne Python und Perl nicht, und obwohl ich es in C ++ tun könnte, ist es Jahre her, seit ich es verwendet habe Ich versuche mich auf Bash zu konzentrieren.

Kurze Perl- und Python-Schnipsel sind gut. Bash wird bevorzugt (aber nicht unter Geschwindigkeitseinbußen).

Peter.O
quelle
1
Wie sehen diese Bytes aus? 4 125 -19 0 kann als 4 Bytes angesehen werden, "\ t.-X" kann als 4 Bytes angesehen werden (ich wähle 4, weil es kürzer ist), aber wie wäre es mit 319 und "ö§« ¢ "? Oder sind es die ersten 8 Bytes in einer beliebigen Datei?
Benutzer unbekannt
'4 125 -19 0' ist nicht 4 Bytes. Es kann die dezimale Ganzzahldarstellung von 4 Bytes sein, die auf ihrem binären Ganzzahlwert interpretiert werden, aber es werden 8 Bytes (ohne Leerzeichen) verwendet. '\ T.-X' ist eine Wiederholung von 4 Bytes in ASCII / 'C. 'Stil; es werden 5 Bytes verwendet. ö§«¢sind keine Bytes; Es handelt sich nominell um Unicode-Zeichen, die bei der Codierung in UTF-8 und UTF-32 8 Byte und bei der Codierung in UTF-32 16 Byte verwenden. Wie in Manpages häufig anzutreffen, beziehe ich mich auf 8-Bit-Oktette. Nur ein gewöhnliches 8-Bit-Byte. Ich möchte 8 davon als 64-Bit-Big-Endian-Int
ohne
Nein, '4 125 -19 0' ist eine gültige, mögliche Darstellung oder 4 Bytes. Die Pixel auf dem Bildschirm verwenden viel mehr als 8 Bytes. "ö§« ¢ "sind natürlich Bytes, da jede digitale Information in Bytes ausgedrückt werden kann - genauso wie jede Entfernung als Meter und als Zoll dargestellt werden kann. Meine Frage ist, wie bekommt man die Bytes, wie werden sie dargestellt? Ein 8-Bit-Oktett wäre beispielsweise 00101101.
Benutzer unbekannt
Meine Eingabebytes sind nicht "dargestellt". Ich spreche von einem Byte, das ein Byte ist ... nicht mehr und nicht weniger ... dh. Rohdaten ... Ich möchte dann 8 dieser Rohbytes als vorzeichenlose lange lange interpretieren, dh. a 64bit unsigned int ... Ich werde diese ganzen Zahlen überhaupt nicht visuell darstellen. Ich werde sie in einer Hash-Berechnung verwenden. ... Ich interessiere mich für die Binärwerte jedes Bytes ... Denken Sie mal darüber nach, das ist die "Darstellung" ... Ich möchte mit dem intrinsischen Basis-2- Wert von 8 Bits arbeiten, dh. Ich mag nicht das Byte in irgendeiner besonderen Weise behandeln.
Peter.O
Ein Byte ist ein Byte und eine Rose ist eine Rose, aber Bash hat kein Typensystem, oder? Nun - ich kenne Strings und Integer-Zahlen und Boolesche Werte in Bash, aber es gibt keinen Bytetyp, also muss ich etwas anderes nehmen - Strings, Zahlen, Boolesche Werte, Arrays, Dateien ... Können Sie einfach zeigen, wie Sie diese Bytes erhalten? dein Skript? Oder sind sie direkt im Skript codiert (roh?)?
Benutzer unbekannt

Antworten:

6

Bash ist das falsche Werkzeug. Muscheln können gut Teile zusammenkleben; Textverarbeitung und Arithmetik werden nebenbei bereitgestellt, und die Datenverarbeitung liegt überhaupt nicht in ihrem Zuständigkeitsbereich.

Ich würde Python über Perl wählen, weil Python auf Anhieb Bignums hat. Verwenden Sie struct.unpackdiese Option, um die Daten zu entpacken.

#!/usr/bin/env python
import os, struct, sys
fmt = "<" + "Q" * 8192
header_bytes = sys.stdin.read(65536)
header_ints = list(struct.unpack(fmt, header_bytes))
sys.stdin.seek(-65536, 2)
footer_bytes = sys.stdin.read(65536)
footer_ints = list(struct.unpack(fmt, header_bytes))
# your calculations here

Hier ist meine Antwort auf die ursprüngliche Frage. Die überarbeitete Frage hat nicht viel mit dem Original zu tun, bei dem es darum ging, eine 8-Byte-Sequenz in die 64-Bit-Ganzzahl umzuwandeln, die sie in Little-Endian-Reihenfolge darstellt.

Ich glaube nicht, dass bash eine eingebaute Funktion dafür hat. Das folgende Snippet wird aauf eine Zeichenfolge gesetzt, die die hexadezimale Darstellung der Zahl ist, die den Bytes in der angegebenen Zeichenfolge in Big-Endian- Reihenfolge entspricht.

a=0x$(printf "%s" "$string" |
      od -t x1 -An |
      tr -dc '[:alnum:]')

Kehren Sie für die Little-Endian-Reihenfolge die Reihenfolge der Bytes in der ursprünglichen Zeichenfolge um. In Bash und für eine Zeichenfolge bekannter Länge können Sie dies tun

a=0x$(printf "%s" "${string:7:1}${string:6:1}${string:5:1}${string:4:1}${string:3:1}${string:2:1}${string:1:1}${string:0:1}" |
      od -t x1 -An |
      tr -dc '[:alnum:]')

Sie können auch die bevorzugte Endianität Ihrer Plattform ermitteln, wenn Sie od8-Byte-Typen unterstützen.

a=0x$(printf "%s" "$string" |
      od -t x8 -An |
      tr -dc '[:alnum:]')

Ob Sie rechnen $akönnen, hängt davon ab, ob Ihre Bash 8-Byte-Arithmetik unterstützt. Selbst wenn dies der Fall ist, wird es als signierter Wert behandelt.

Alternativ können Sie Perl verwenden:

a=0x$(perl -e 'print unpack "Q<", $ARGV[0]' "$string")

Wenn Ihr Perl ohne 64-Bit-Integer-Unterstützung kompiliert wird, müssen Sie die Bytes aufteilen.

a=0x$(perl -e 'printf "%x%08x\n", reverse unpack "L<L<", $ARGV[0]' "$string")

(Ersetzen Sie es <durch >Big-Endian oder entfernen Sie es, um die Plattform-Endianness zu erhalten.)

Gilles 'SO - hör auf böse zu sein'
quelle
@Gille; ... Ich sehe mir immer noch Ihre Optionen an (Geschwindigkeit; ich verarbeite 64 KB), aber nebenbei bin ich mir ziemlich sicher, dass Little-Endian nur das Byte niedrigster Ordnung in den oberen Bereich verschoben hat -Ordnung Position ... Es ist keine vollständige Umkehrung für größere Wortgrößen ... Es war eine vollständige Umkehrung, wenn Wörter 16 Bit waren :)
Peter.O
@fred: Ah, du hast nicht gesagt, dass du Geschwindigkeit willst. Perl wird zweifellos gewinnen, es sei denn, Sie massieren diese Ausgabe besser. Welche der Little-Endian-Versionen funktioniert nicht? Ich bekomme die Ausgabe, die ich erwarte.
Gilles 'SO - hör auf böse zu sein'
@ Gilles: Ups! Entschuldigung ... Sie haben Recht mit der Bytereihenfolge. es ist eine vollständige Umkehrung ... (Ich habe lange gewusst (?) , dass es nur das Byte niedriger Ordnung war, und es ist eine gute Sache, dass ich mein Wissen bis heute noch nie nutzen musste :) .. .. re perl , ich bekomme diesen Fehler, wenn ich "12345678" als Zeichenfolge verwende ..
Peter.O
@fred: Welcher Fehler? Welchen Befehl haben Sie ausgeführt, was war die Ausgabe, was haben Sie erwartet?
Gilles 'SO - hör auf böse zu sein'
@ Gilles: Die Fehlermeldung: Invalid type 'Q' in unpack at -e line 1.... Der Befehl (mit einer Zeichenfolge von 8 zufälligen Bytes): perl -e 'print unpack "Q<", $ARGV[0]' "12345678"... Die Ausgabe: nur die Fehlermeldung ... perl vereion: v5.10.1
Peter.O
4

Gilles 'Python-Methode ist definitiv schneller, aber ich dachte, ich würde diese * Bash *** + *** Standard-Single-Purpose-Tools * einfach als allgemeines Mahlgut in die Mühle werfen. Es geht wahrscheinlich genauso um' bc ' wie alles andere ... Es gibt eine Menge Initialisierungsmaterial, um Eingabedateien mit weniger als 64 KB zu berücksichtigen ... Der Hash wird auf die Länge der Datei initialisiert, und dann wird jede der 64-Bit-Ganzzahlen nacheinander hinzugefügt es; verursacht (erwarteten) Integer-Überlauf .. hat es bcgeschafft, den Trick zu machen ...

# This script reads 8196 8-byte blocks (64 KiB) from the head and tail of a file
# Each 8-bytes block is interpreted as an unsigned 64-bit Little-Endian integer.
# The head integers and tail integers ar printed to stdout; one integer per line.
#
# INIT: If the file is smaller than 64k, calculate the number of unsigned ints to read 
# ====
  file="$1"
  flen=($(du -b "$file"))           # file length
  qlen=8                            # ui64 length in bytes
    ((flen<qlen)) && exit 1         # file is too short -- exit 
  bmax=$((64*1024))                 # byte end of read (== byte max to read)
    ((flen<bmax)) && ((bmax=flen))  # reduce byte max to file length
  qmax=$((bmax/qlen))               # ui64 end of read (== ui64 max to read)
    (((qmax*qlen)<bmax)) && ((bmax=(qmax*qlen))) # round down byte max (/8)
  hash=$(echo $flen |xxd -p -u)
# 
# MAIN
# ====
  for skip in 0  $((flen-bmax)) ;do
    hash=$(dd if="$file" bs=1 count=$bmax skip=$skip 2>/dev/null |
             xxd -p -u -c 8 |
             { echo -e " ibase=16 \n obase=10 \n scale=0 \n hash=$hash \n ouint=10000000000000000 "; \
               sed -re "s/(..)(..)(..)(..)(..)(..)(..)(..)/hash=(hash+\8\7\6\5\4\3\2\1)%ouint/"; \
               echo "hash"; } |bc)
  done
  echo $hash
#

# Output:
16A6528E803325FF
Peter.O
quelle
0

Dies akzeptiert stdin und druckt die letzten 64 KB der Datei als vorzeichenlose 8-Byte-Hexadezimalzahlen in der Endianess des Computers (Little-Endian auf x86). Um die ersten 64 KB zu drucken, ersetzen Sie "Schwanz" durch "Kopf".

tail -c $(( 1024*64 )) | xxd -ps |tr -d '\n' | while read -N16 i ; do echo 0x$i ; done

EINSCHRÄNKUNGEN: Der Versuch, die Ausgabe mit printf in eine Dezimalzahl umzuwandeln, führt zu Fehlern außerhalb des Bereichs

user2141130
quelle