Niehztog: unpack von binärdaten

Hallo,

1. Problem:
ich habe eine Datei, in die drei Zahlen wie folgt reingeschrieben werden:

  
$handle = fopen('test.bin', "a"));  
fwrite($handle, pack ("VVV", 1262698832, 44927, 128)));  
fclose($handle);  

Mein Ziel ist es nun, diese Werte später auch wieder aus der Datei zu lesen. Momentan klappt es bei mir so:

  
$handle = fopen('test.bin', 'rb');  
  
$data = fread($handle, 4);  
$raw = unpack("V", $data);  
$zahl1 = $raw[1];  
  
$data = fread($handle, 4);  
$raw = unpack("V", $data);  
$zahl2 = $raw[1];  
  
$data = fread($handle, 4);  
$raw = unpack("V", $data);  
$zahl3 = $raw[1];  
  
fclose($handle);  

Gibt es eine Möglichkeit, die Zahlen "eleganter" auszulesen - also mit einem einzigen unpack Befehl? Ich habe versucht unpack("VVV", $data), wobei in $data dann auch alles drin stand. Das klappte irgendwie nicht, es kam nur die erste Zahl heraus.

2. Problem:
Mit folgendem Befehl werden binärdaten gepackt:

  
$data = pack('a56@56VV', 'irgendeinstring', 44927, 128);  

Hier versteh ich den pack Befehl noch nicht mal. Ich habe keine Ahnung wie ich die drei Ursprungswerte mit unpack zurück bekomme. Kann mir da jemand helfen?

mfG
Niehztog

  1. hi,

    Hier versteh ich den pack Befehl noch nicht mal. Ich habe keine Ahnung wie ich die drei Ursprungswerte mit unpack zurück bekomme. Kann mir da jemand helfen?

    Du brauchst bei pack/unpack _stets_ die gleiche Schablone. Also, wenn Du 3 Zahlen packst, ist "VVV" ok, das ergibt eine bytefolge in "VAX" (little-endian) order.

    Daraus kriegst Du mit
    unpack "VVV", $binary;

    eine Liste mit den drei Werten. Also Listen-Kontext beachten.

    Hotti

    1. Hallo Tom und hotti,

      danke für eure Tips. Genau wie ihr das beschreibt hatte ich mir das auch vorgestellt, aber es klappt nicht. Folgender Code:

        
      $var1 = pack('VVV', 1262698832, 44927, 128 );  
      $var2 = unpack('VVV', $var1);  
      var_dump($var2);  
      
      

      Output:

        
      array(1) {  
        ["VV"]=>  
        int(1262698832)  
      }  
      
      

      Wie man sieht gibt es nur ein Array-Element anstelle der erwarteten drei. Was läuft schief?

      1. [latex]Mae  govannen![/latex]

        Hallo Tom und hotti,

        danke für eure Tips. Genau wie ihr das beschreibt hatte ich mir das auch vorgestellt, aber es klappt nicht. Folgender Code:

        $var1 = pack('VVV', 1262698832, 44927, 128 );
        $var2 = unpack('VVV', $var1);
        var_dump($var2);

          
        'VVV'? Nö.  
        Du suchst das Wiederholungszeichen \* im Format-String  
          
        Cü,  
          
        Kai
        
        -- 
        ~~~ ken SENT ME ~~~  
        Dank Hixies Idiotenbande geschieht grade eben wieder ein Umdenken  
        in Richtung "Mess up the Web".([suit](https://forum.selfhtml.org/?t=197497&m=1324775))  
        [SelfHTML-Forum-Stylesheet](http://selfhtml.knrs.de/#h_stylesheet)
        
      2. Hello,

        Hallo Tom und hotti,

        danke für eure Tips. Genau wie ihr das beschreibt hatte ich mir das auch vorgestellt, aber es klappt nicht. Folgender Code:

        $var1 = pack('VVV', 1262698832, 44927, 128 );
        $var2 = unpack('VVV', $var1);
        var_dump($var2);

        
        >   
        > Output:  
        > ~~~
          
        
        > array(1) {  
        >   ["VV"]=>  
        >   int(1262698832)  
        > }  
        > 
        
        

        Wie man sieht gibt es nur ein Array-Element anstelle der erwarteten drei. Was läuft schief?

        Es ist auch sehr gewöhnungsbedürftig:

          
        <?php   ### pack_unpack.php ###  
          
            $var1 = pack('VVV', 1262698832, 44927, 128 );  
            $var2 = unpack('V1myvar/V1yourvar/V1ourvar', $var1);  
            var_dump($var2);  
          
        ?>  
        
        

        das ergibt:

        array(3) { ["myvar"]=> int(1262698832) ["yourvar"]=> int(44927) ["ourvar"]=> int(128) }

        Zuerst kommt immer das Format/der Typ, dann der Wiederholungsfaktor und danach der Name des Array-Elementes.

        Liebe Grüße aus dem schönen Oberharz

        Tom vom Berg

        --
         ☻_
        /▌
        / \ Nur selber lernen macht schlau
        http://bergpost.annerschbarrich.de
        1. Klasse, mit 'V1myvar/V1yourvar/V1ourvar' klappt es, ebenso wie mit 'V*' und 'V3'. Vielen Dank dafür.

          Eine letzte Frage hätte ich noch (ich lese tatsächlich ein externes Dateiformat ein): Gibt es auch einen Format String, der bewirkt, dass die binärdaten nur bis zum ersten NUL Byte gelesen werden? Im konkreten Fall kommt erst ein 56 byte langer string und dann zwei 4 byte integer. Allerdings kommt es vor, dass im String (der nicht immer die ganzen 56 byte ausnutzt) nach dem ersten NUL Byte noch mülldaten kommen, die von unpack dann als komische Zeichen an den entpackten String drangehängt werden.

          Klar, man könnte den Sting byte für byte lesen und nach dem ersten NUL Byte aufhören, aber vielleicht geht das ja auch "eleganer"?

          1. Hello,

            Klasse, mit 'V1myvar/V1yourvar/V1ourvar' klappt es, ebenso wie mit 'V*' und 'V3'. Vielen Dank dafür.

            Eine letzte Frage hätte ich noch (ich lese tatsächlich ein externes Dateiformat ein): Gibt es auch einen Format String, der bewirkt, dass die binärdaten nur bis zum ersten NUL Byte gelesen werden? Im konkreten Fall kommt erst ein 56 byte langer string und dann zwei 4 byte integer. Allerdings kommt es vor, dass im String (der nicht immer die ganzen 56 byte ausnutzt) nach dem ersten NUL Byte noch mülldaten kommen, die von unpack dann als komische Zeichen an den entpackten String drangehängt werden.

            Klar, man könnte den Sting byte für byte lesen und nach dem ersten NUL Byte aufhören, aber vielleicht geht das ja auch "eleganer"?

            Lies einen Block mit einer byteorientierten Dateifunktion ein, werte ihn mit einer ebensolchen "Stringfunktion" aus.

            $packed_values = '';

            if(($pos = strpos($buffer, chr(0))!==0)
               {
                   $packed_values = substr($buffer, 0, $pos);
               }

            und entscheide dann, wieviele Werte Du auswerten musst.

            http://de.php.net/manual/en/function.strpos.php
            http://de.php.net/manual/en/function.substr.php

            Soweit ich mich erinnere, kann man bei der Angabe der Maske für die Decodierung (unpack) der Werte auch mehr angeben, als nachher tatsächlich vorhanden sind. Das solltest Du aber selber nochmal ausprobieren. Das hieße, dass Du nur den auszuwertenden Bytestrom entsprechend obigem Beispiel kürzen müsstest und dann nur soviele Werte daraus generiert werden, wie Futter dafür vorhanden ist.

            Liebe Grüße aus dem schönen Oberharz

            Tom vom Berg

            --
             ☻_
            /▌
            / \ Nur selber lernen macht schlau
            http://bergpost.annerschbarrich.de
            1. Hello,

              $packed_values = '';

              ###>    if(($pos = strpos($buffer, chr(0))!==0)
                   if(($pos = strpos($buffer, chr(0))!==false)

              {
                     $packed_values = substr($buffer, 0, $pos);
                 }

              und entscheide dann, wieviele Werte Du auswerten musst.

              http://de.php.net/manual/en/function.strpos.php
              http://de.php.net/manual/en/function.substr.php

              Soweit ich mich erinnere, kann man bei der Angabe der Maske für die Decodierung (unpack) der Werte auch mehr angeben, als nachher tatsächlich vorhanden sind. Das solltest Du aber selber nochmal ausprobieren. Das hieße, dass Du nur den auszuwertenden Bytestrom entsprechend obigem Beispiel kürzen müsstest und dann nur soviele Werte daraus generiert werden, wie Futter dafür vorhanden ist.

              Liebe Grüße aus dem schönen Oberharz

              Tom vom Berg

              --
               ☻_
              /▌
              / \ Nur selber lernen macht schlau
              http://bergpost.annerschbarrich.de
              1. Hallo,

                $packed_values = '';

                if(($pos = strpos($buffer, chr(0))!==false)

                {
                       $packed_values = substr($buffer, 0, $pos);
                   }

                und entscheide dann, wieviele Werte Du auswerten musst.

                Hat so wunderbar geklappt. Gute Idee es so zu lösen (auch wenn strpos intern wahrscheinlich auch über die einzelnen Zeichen iteriert - sieht so aber schöner aus). ;-)

                  
                $pos = strpos($buffer, chr(0));  
                $data[] = unpack('a' . ( $pos ? (string)$pos : '56' ) . 'var1/@56/V1var2/V1var3', $buffer);  
                
                

                Vielen Dank!

          2. hi,

            Klar, man könnte den Sting byte für byte lesen und nach dem ersten NUL Byte aufhören, aber vielleicht geht das ja auch "eleganer"?

            Die Schablone "Z*" liefert mit pack einen Nullterminierten String. Genauso kriegst Du den mit unpack("Z*", $bin); wieder raus. Alles, was danach kommt, wird dann ignoriert, zumindest habe ich das mal eben mit Perl getestet. Du könntest evntl. danach die Position des Dateizeigers (im Handle) abfragen, damit die Datei weiter gelesen werden kann (experimental, nicht getestet).

            Hotti

            1. Hi hotti

              Die Schablone "Z*" liefert mit pack einen Nullterminierten String.

              Gerade getestet: Type Z: unknown format code
              Sieht so aus, dass Z in PHP noch nicht implementiert ist. Wenn doch, hätte es mir sicher geholfen und wäre vermutlich an dieser Stelle genau das richtige. Schonmal gut zu wissen, dass es dann wohl also wirklich nicht anders geht als Byte für Byte einzulesen.

              Niehztog

              1. hi,

                Die Schablone "Z*" liefert mit pack einen Nullterminierten String.

                Gerade getestet: Type Z: unknown format code
                Sieht so aus, dass Z in PHP noch nicht implementiert ist. Wenn doch, hätte es mir sicher geholfen und wäre vermutlich an dieser Stelle genau das richtige. Schonmal gut zu wissen, dass es dann wohl also wirklich nicht anders geht als Byte für Byte einzulesen.

                Oder: Machs mit Perl ;-)

                Mein Hobby seit über 10 Jahren, und so richtig "Gas gegeben mit Perl" habe ich im ersten Quartal dieses Jahr, indem ich ein paar eigene Module geschrieben habe zum Speichern von Objekten in Binär(raw)dateien.

                Auf jeden Fall finde ich es begrüßenswert, dass sich da auch in Sachen PHP was tut, an der Web-Front, wo es auf Performance ankommt, sind Binärdateien in vielen Fällen einer DB-Anbindung klar überlegen ohne den Anspruch zu erheben, dass eine solche Datenhaltung bspw. eine MySQL-Anbindung grundsätzlich ersetzen kann. Das ist freilich von Fall zu Fall abzuwägen.

                Viel Spaß weiterhin,
                Horst

  2. Hello,

    Mein Ziel ist es nun, diese Werte später auch wieder aus der Datei zu lesen. Momentan klappt es bei mir so:
    Gibt es eine Möglichkeit, die Zahlen "eleganter" auszulesen - also mit einem einzigen unpack Befehl?

    Unpack() liefert ein Array mit den Elementen. Die Namen der Arrayelemente gibst Du durch die passende Schablone vor.

    http://de.php.net/manual/en/function.unpack.php
    http://de.php.net/manual/en/function.pack.php

    Liebe Grüße aus dem schönen Oberharz

    Tom vom Berg

    --
     ☻_
    /▌
    / \ Nur selber lernen macht schlau
    http://bergpost.annerschbarrich.de
  3. Hello,

    wenn es nicht um vorgegebene Dateiformate geht und die Anzahl der Datenwerte überschaubar belibt, dann sind innerhalb von PHP-Applikationen (also ohne Zugriff auf Fremddaten) serialize() und unserialize() eher zu empfehlen

    http://de.php.net/manual/en/function.serialize.php
    http://de.php.net/manual/en/function.unserialize.php

    Liebe Grüße aus dem schönen Oberharz

    Tom vom Berg

    --
     ☻_
    /▌
    / \ Nur selber lernen macht schlau
    http://bergpost.annerschbarrich.de