PHP Doku:: Entpackt die Daten eines Binär-Strings - function.unpack.html

Verlauf / Chronik / History: (1) anzeigen

Sie sind hier:
Doku-StartseitePHP-HandbuchFunktionsreferenzSonstige GrunderweiterungenMiscellaneous FunctionsSonstige Funktionenunpack

Ein Service von Reinhard Neidl - Webprogrammierung.

Sonstige Funktionen

<<uniqid

usleep>>

unpack

(PHP 4, PHP 5)

unpackEntpackt die Daten eines Binär-Strings

Beschreibung

array unpack ( string $format , string $data )

Überträgt die Daten eines Binär-Strings in ein Array unter Berücksichtigung des format-Parameters.

unpack() funktioniert etwas anders als man es von Perl her kennt, da sich die zurückgegebenen Daten in einem assoziativen Array befinden. Dies erreichen Sie nur, wenn Sie die verschiedenen Format-Codes benennen und sie mit einem "/" voneinander trennen.

Parameter-Liste

format

Die Format-Codes werden unter pack() näher erläutert.

data

Die gepackten Daten.

Rückgabewerte

Gibt ein assoziatives Array zurück, dass die entpackten Elemente als Binärzeichenkette enthält.

Beispiele

Beispiel #1 unpack()-Beispiel

<?php
$array 
unpack("c2chars/nint"$binarydata);
?>

Das resultierende Array wird die Einräge "chars1", "chars2" und "int" enthalten.

Anmerkungen

Achtung

Beachten Sie, dass PHP Integer-Werte intern mit Vorzeichen speichert. Wenn Sie einen großen vorzeichenlosen Longwert entpacken und er von der selben Größe ist, die PHP für dessen Speicherung verwendet, wird das Ergebnis ein negativer Wert sein (auch wenn Sie dieses als vorzeichenlos zu entpacken angegeben haben).

Siehe auch

  • pack() - Packt Daten in eine Binär-Zeichenkette


28 BenutzerBeiträge:
- Beiträge aktualisieren...
Anonymous
4.01.2011 16:51
Between php version 5.1 and 5.1.3 unpack("H*hex", $data) returned wrong data due to bug http://bugs.php.net/36148.
zac at picolink dot net
22.10.2010 17:33
The documentation is clear that an integer read using an unsigned format character will still be stored as a signed integer.  The often-cited work-around is to use sprintf('%u', $bigint) to properly display integers with the MSB set.

In the case where the numeric value is more important than how it's displayed, you can still work with other large integers using intval() to "upgrade" your existing unsigned integers.

I had a problem comparing 32-bit integers read from files with hard-coded constants (file signatures tend to need this).  Here's what I did to avoid converting everything into strings:

<?php

$bigint
= 0x89504E47;

$packed = pack('N', $bigint);

list(
$unpacked) = array_values(unpack('N', $packed));

//The $bigint remains an unsigned integer.
//Even though their bit-wise values are identical, comparison fails.

echo 'bigint ',
  (
$bigint == $unpacked ? '==' : '!='),
 
" unpacked\n";

//intval() triggers a re-interpretation of $bigint.
//$bigint is internally compared as a signed integer.
//Since the bit-wise value of $bigint never changes, comparison succeeds.

echo 'intval(bigint) ',
  (
intval($bigint) == $unpacked ? '==' : '!='),
 
" unpacked\n";

?>

It works, but it's a little backwards.  If anyone has any ideas on how to "downgrade" a signed integer into an unsigned integer without using strings, that would be a valuable note to add to the documentation.
Aaron Wells
8.10.2010 12:06
Another option for converting binary data into PHP data types, is to use the Zend Framework's Zend_Io_Reader class:
http://bit.ly/9zAhgz

There's also a Zend_Io_Writer class that does the reverse.
norwood at computer dot org
6.04.2010 23:15
Reading a text cell from an Excel spreadsheet returned a string with low-order embedded nulls: 0x4100 0x4200 etc. To remove the nulls, used

<?php
$strWithoutNulls
= implode( '', explode( "\0", $strWithNulls ) );
?>

(unpack() didn't seem to help much here; needed chars back to re-constitute the string, not integers.)
Anonymous
24.09.2009 5:02
Functions I found useful when dealing with fixed width file processing, related to unpack/pack functions.
<?php
/**
* funpack
* format: array of key, length pairs
* data: string to unpack
*/
function funpack($format, $data){
    foreach (
$format as $key => $len) {
       
$result[$key] = trim(substr($data, $pos, $len));
       
$pos+= $len;
    }
    return
$result;
}

/**
* fpack
* format: array of key, length pairs
* data: array of key, value pairs to pack
* pad: padding direction
*/
function fpack($format, $data, $pad = STR_PAD_RIGHT){
    foreach (
$format as $key => $len){
       
$result .= substr(str_pad($data[$key], $len, $pad), 0, $len);
    }
    return
$result;
}
?>
sica at wnet com br
20.07.2009 21:45
The script following is a example how to save more than one values on file separating its with "\r\n" and how to recovering its values.

<?php
// Save two integer values in a binary file
$nomearq = "./teste.bin";
$valor = 123;
$ptrarq = fopen($nomearq, "wb");
$valorBin = pack("L",$valor);
echo
"First value ($valor) packed with ";
echo
fwrite($ptrarq, $valorBin)." bytes<br>";
echo
"Separator \\r\\n with ";
echo
fwrite($ptrarq, "\r\n")." bytes<br>";
$valor = 456;
$valorBin = pack("L",$valor);
echo
"Second value ($valor) packed with ";
echo
fwrite($ptrarq, $valorBin)." bytes<br>";
fclose($ptrarq);

// Recover the saved values
$ptrarq = fopen($nomearq, "rb");
$valorBin = file($nomearq,filesize($nomearq));
echo
"<br>The reading values is:<br>";
foreach(
$valorBin as $valor){
 
$valor = unpack("L",$valor);
 
print_r($valor);
  echo
"<br>";
}
fclose($ptrarq);
?>

Results:
First value (123) packed with 4 bytes
Separator \r\n with 2 bytes
Second value (456) packed with 4 bytes

The reading values is:
Array ( [1] => 123 )
Array ( [1] => 456 )
jlarsen at fsu dot edu
10.12.2008 0:40
As with perl, the count for hex is number of nybbles or half-bytes, this differs from the other options which count in full bytes.
Nhon
21.05.2008 22:42
As stated above, "if you unpack a large unsigned long and it is of the same size as PHP internally stored values the result will be a negative number even though unsigned unpacking was specified."

To restore the original unsigned value, you could do this :

if ($unpackedVal <0)
{
      $unpackedVal += 4294967296;
}

Hope this helps !

Cheers
Anonymous Coward
28.03.2008 19:52
Warning: This unpack function makes the array with keys starting at 1 instead of starting at 0.

For example:
<?php
 
function read_field($h) {
 
$a=unpack("V",fread($h,4));
  return
fread($h,$a[1]);
 }
?>
joe dot nemeth @ palg dot com
8.02.2008 19:29
A simpler solution is to mask the value with 0xffffffff. For instance:

<?php
$rec
= unpack(
 
"Vvalue/".
 
"Vhash32/",
 
$recbin);
$rec['hash32'] &= 0xffffffff;
$rec['value'] &= 0xffffffff;
?>

Unlike sprintf(), which converts the value to a string, this preserves the numeric type of the value.
Shawn Kelly
6.02.2008 21:14
Above it says this:

  "Note that PHP internally stores integral values as signed. If  you unpack a large unsigned long and it is of the same size as PHP internally stored values the result will be a negative number even though unsigned unpacking was specified."

This happened to me.  I wanted to get a big number from a unsigned long, but it kept coming returning a negative.  Happened to notice that sprintf('%u',$dta) will take the useless negative and restore it into its large unsigned proper magnitude.

Hope this saves someone a little time...
phpclub at rambler dot ru
7.11.2007 13:28
Correct substr 

If you are trying to make unpack 'N' work with unsigned long on 64 bit machines, you should take a look to this bug:
http://bugs.php.net/bug.php?id=40543

An ugly workaround for me was:

//Read a 4-byte integer from file
      $_r = fread($f,4);
      $a=unpack('Ni',$_r);
      $b = sprintf("%b", $a['i']); // binary representation
      if(strlen($b) == 64){
           $new = substr($b, 32);
           $a['i'] = bindec($new);
       }
Carsten Meier
14.09.2007 15:12
The previous code has an error:
$low = $val & 65536;
has to be:
$low = $val & 65535;
Otherwise only bit #16 gets masked an not the lower 16 bit.
ein at anti-logic dot com
3.08.2007 0:13
If you need to unpack an unsigned long that's larger than php's limit (2^31-1) and don't mind doing a lot of fiddling, an easy solution is to unpack it as two shorts.

For example

<?php
$val
= 2147483647; // we can't actually assign a number larger than 2^31-1 in php to an int, but if you're reading it from outside, it's easy to get larger than that.
// split it into two 16 bit words
$high= $val>>16;
$low = $val & 65536;
$packed= pack('nn',$high,$low);

// we have a 32bit long packed number assigned to $packed
$unpacked = unpack('nhigh\nlow',$packed);
$final = ($unpacked['high'] << 16) + $unpacked['low'];
?>

Of course this works equally as well as unpacking an externally created unsigned long.
This is most usefull when dealing with bitmasks, where the high and low word don't need to be connected.

Working with signed values is also possible, but I don't have any code examples to share (no need for them myself).
elpaso
6.03.2007 14:38
If you are trying to make unpack 'N' work with unsigned long on 64 bit machines, you should take a look to this bug:
http://bugs.php.net/bug.php?id=40543

An ugly workaround for me was:

//Read a 4-byte integer from file
      $_r = fread($f,4);
      $a=unpack('Ni',$_r);
      $b = sprintf("%b", $a['i']); // binary representation
      if(strlen($b) == 64){
           $new = substr($b, 33);
           $a['i'] = bindec($new);
       }
eric dot brison at anakeen dot com
25.01.2007 11:25
An simple function to decode active directory sid
function sid_decode($osid) {
  $sid=false;
  $u=unpack("H2rev/H2b/nc/Nd/V*e", $osid);
  if ($u) {
    $n232=pow(2,32);
    unset($u["b"]); // unused
    $u["c"]= $n232*$u["c"]+$u["d"];
    unset($u["d"]);
    $sid="S";
    foreach ($u as $v) {
      if ($v < 0) $v=$n232 + $v;
      $sid.= "-".$v;
    }
  }
  return $sid;
}
// example
$osid64="AQUAAAAAAAUVAAAA3DixrE8XmGks/zdlAwIAAA==";
print sid_decode(base64_decode($osid64));

// display : S-01-5-21-2897295580-1771575119-1698168620-515
Justin dot SpahrSummers at gmail dot com
8.10.2005 21:10
I hadn't realized that if the number after the unpack type was 1 (i.e. "V1page"), that it would behave as if there was no number at all. I had been using a variable and didn't think to watch for this. For instance,

<?php

if ($something)
  
$get = 2;
else
  
$get = 1;

$arr = unpack("V" . $get . "page", $data);

?>

Now if $something was FALSE, then $arr will only have one entry named "page". If $something was TRUE, $arr would have "page1" and "page2".
Cristiano - finatofinato at yahoo dot com dot br
19.07.2005 15:31
STORE FILES IN DATABASE SQL_SERVER

After days of research, I obtained a solution to my "big-problem" working with php5/sqlserver/iis

//IN PHP.INI
//Valid range 0 - 2147483647.  Default = 4096.
mssql.textsize = 2147483647

=============<INSERT>==================
$dataString = file_get_contents($_FILES['myfile']['tmp_name']);
$arrData = unpack("H*hex", $dataString);
$ds = "0x".$arrData['hex'];

$sql = "SET TEXTSIZE 2147483647";
mssql_query($sql, $conn);   
$sql = " insert into table (myimage, name, extension) values (".$ds.", '".$name."', '".$extension."') ";
mssql_query($sql, $conn);   
=============</INSERT>==================

=============<RETRIEVE>==================
$sql = "SET TEXTSIZE 2147483647";
@mssql_query($sql, $conn);   
$sql = " select myimage, name, extension from table ";
$rs = @mssql_query($sql, $conn);   

header("Content-Disposition: attachment; filename=".$rs['name'].".".$rs['extension']." ");
echo $rs['myfile'];       

=============</RETRIEVE>==================

with apache does not work in some cases (work with txt files, but not work with pdf, .doc....), and you will try to do another solution (the solution is more simple than this of sqlserver)
info at dreystone dot com
4.05.2005 20:31
Here is my solution to reading a Big-Endian formatted double on an Little-Endian machine.

<?php

function ToDouble($data) {
   
$t = unpack("C*", pack("S*", 256));
    if(
$t[1] == 1) {
       
$a = unpack("d*", $data);
    } else {
       
$a = unpack("d*", strrev($data));
    }
    return (double)
$a[1];
}

?>
MagicalTux at FF dot st
28.04.2005 16:17
tomlove at gmail dot com gave a code to easily unpack a string. Here's a variant :

<?php
$packed
= pack("s*", 123);
list(
$unpacked) = unpack("s*0", $packed);
?>

$unpacked now holds the value 123, not an array.

unpack() will use numeric keys if you give only numbers (probably same check as is_numeric(), but I didn't test). This can be useful in some cases (like for getting directly your data without using arrays).
jjfoerch at earthlink dot net
21.10.2004 13:57
I had a situation where I had to unpack a file filled with little-endian order double-floats in a way that would work on either little-endian or big-endian machines.  PHP doesn't have a formatting code that will change the byte order of doubles, so I wrote this workaround.

<?php
/*The following code is a workaround for php's unpack function
which does not have the capability of unpacking double precision
floats that were packed in the opposite byte order of the current
machine.
*/
function big_endian_unpack ($format, $data) {
   
$ar = unpack ($format, $data);
   
$vals = array_values ($ar);
   
$f = explode ('/', $format);
   
$i = 0;
    foreach (
$f as $f_k => $f_v) {
   
$repeater = intval (substr ($f_v, 1));
    if (
$repeater == 0) $repeater = 1;
    if (
$f_v{1} == '*')
    {
       
$repeater = count ($ar) - $i;
    }
    if (
$f_v{0} != 'd') { $i += $repeater; continue; }
   
$j = $i + $repeater;
    for (
$a = $i; $a < $j; ++$a)
    {
       
$p = pack ('d',$vals[$i]);
       
$p = strrev ($p);
        list (
$vals[$i]) = array_values (unpack ('d1d', $p));
        ++
$i;
    }
    }
   
$a = 0;
    foreach (
$ar as $ar_k => $ar_v) {
   
$ar[$ar_k] = $vals[$a];
    ++
$a;
    }
    return
$ar;
}

list (
$endiantest) = array_values (unpack ('L1L', pack ('V',1)));
if (
$endiantest != 1) define ('BIG_ENDIAN_MACHINE',1);
if (
defined ('BIG_ENDIAN_MACHINE')) $unpack_workaround = 'big_endian_unpack';
else
$unpack_workaround = 'unpack';
?>

This workaround is used like this:

<?php

function foo() {
        global
$unpack_workaround;
   
$bar = $unpack_workaround('N7N/V2V/d8d',$my_data);
//...
}

?>

On a little endian machine, $unpack_workaround will simply point to the function unpack.  On a big endian machine, it will call the workaround function.

Note, this solution only works for doubles.  In my project I had no need to check for single precision floats.
kennwhite dot nospam at hotmail dot com
28.08.2004 21:32
If having a zero-based index is useful/necessary, then instead of:

$int_list = unpack("s*", $some_binary_data);

 try:

$int_list = array_merge(unpack("s*", $some_binary_data));

This will return a 0-based array:

$int_list[0] = x
$int_list[1] = y
$int_list[2] = z
...

rather than the default 1-based array returned from unpack when no key is supplied:

$int_list[1] = x
$int_list[2] = y
$int_list[3] = z
...

It's not used often, but array_merge() with only one parameter will compress a sequentially-ordered numeric-index, starting with an index of [0].
Sergio Santana: ssantana at tlaloc dot imta dot mx
9.07.2004 19:54
This is about the last example of my previous post. For the sake of clarity, I'm including again here the example, which expands the one given in the formal documentation:

<?
  $binarydata
= "AA\0A";
 
$array = unpack("c2chars/nint", $binarydata);
  foreach (
$array as $key => $value)
     echo
"\$array[$key] = $value <br>\n";
?>

This outputs:

$array[chars1] = 65
$array[chars2] = 65
$array[int] = 65

Here, we assume that the ascii code for character 'A' is decimal 65.

Remebering that the format string structure is:
<format-code> [<count>] [<array-key>] [/ ...],
in this example, the format string instructs the function to
  1. ("c2...") Read two chars from the second argument ("AA ...),
  2. (...chars...) Use the array-keys "chars1", and "chars2" for
      these two chars read,
  3. (.../n...) Read a short int from the second argument (...\0A"),
  4. (...int") Use the word "int" as the array key for the just read
      short.

I hope this is clearer now,

Sergio.
Sergio Santana: ssantana at tlaloc dot imta dot mx
9.07.2004 4:41
Suppose we need to get some kind of internal representation of an integer, say 65, as a four-byte long. Then we use, something like:

<?
  $i
= 65;
 
$s = pack("l", $i); // long 32 bit, machine byte order
 
echo strlen($s) . "<br>\n";
  echo
"***$s***<br>\n";
?>

The output is:

X-Powered-By: PHP/4.1.2
Content-type: text/html

4
***A***

(That is the string "A\0\0\0")

Now we want to go back from string "A\0\0\0" to number 65. In this case we can use:

<?
  $s
= "A\0\0\0"; // This string is the bytes representation of number 65
 
$arr = unpack("l", $s);
  foreach (
$arr as $key => $value)
     echo
"\$arr[$key] = $value<br>\n";
?>

And this outpus:
X-Powered-By: PHP/4.1.2
Content-type: text/html

$arr[] = 65

Let's give the array key a name, say "mykey". In this case, we can use:

<?
  $s
= "A\0\0\0"; // This string is the bytes representation of number  65
 
$arr = unpack("lmykey", $s);
  foreach (
$arr as $key => $value)
     echo
"\$arr[$key] = $value\n";
?>

An this outpus:
X-Powered-By: PHP/4.1.2
Content-type: text/html

$arr[mykey] = 65

The "unpack" documentation is a little bit confusing. I think a more complete example could be:

<?
  $binarydata
= "AA\0A";
 
$array = unpack("c2chars/nint", $binarydata);
  foreach (
$array as $key => $value)
    echo
"\$array[$key] = $value <br>\n";
?>

whose output is:

X-Powered-By: PHP/4.1.2
Content-type: text/html

$array[chars1] = 65 <br>
$array[chars2] = 65 <br>
$array[int] = 65 <br>

Note that the format string is something like
<format-code> [<count>] [<array-key>] [/ ...]

I hope this clarifies something

Sergio
David Alsbury
25.04.2003 11:04
This is the best example I have found to use unpack.  I was finally able to make sense of the documentation afters seeing how they used the function.

http://fooassociates.com/phpfer/html/rn45re878.html

Quick Example:

<?
header
("Content-type: text/plain");

/*
N unsigned long (always 32 bit, big endian byte order)
N = 4 bytes
"N2int_var" will read 8 bytes into 2 vars int_var1 and int_var2

binary string A324 = decimal 1,093,874,228
binary string &*12 = decimal 640,299,314

C unsigned char
"Cchar_var" will read 1 byte into char_var

binary string J = decimal 74
*/

$assoc_array = unpack("N2int_var/Cchar_var", "A324&*12J");
echo
"int_var1 = ", $assoc_array['int_var1'], "\n";
echo
"int_var2 = ", $assoc_array['int_var2'], "\n";
echo
"char_var = ", $assoc_array['char_var'], "\n";
?>

Sample Output:
int_var1 = 1093874228
int_var2 = 640299314
char_var = 74
adam at adeptsoftware dot com
17.06.2002 6:01
If you just want to extract a dword/long int from a binary string, the following code works beautifully (intel endian):

$Number = ord($Buffer{0}) | (ord($Buffer{1})<<8) | (ord($Buffer{2})<<16) | (ord($Buffer{3})<<24);
DanRichter.at.programmer.dot.net
10.04.2001 20:26
If no key name is given [e.g., unpack('C*',$data)], the keys are simply integers starting at 1, and you have a standard array. (I know of no way to get the array to start at zero.)

If you use multiple types, you must give a key name for all of them (except optionally one), because the key counter is reset with each slash. For example, in unpack('n2/C*',$data), indices 1 and 2 of the returned array are filled by integers ('n'), then overwritten with characters ('C').
iredden at redden dot on dot ca
12.03.2000 1:34
<?php

function parse_pascalstr($bytes_parsed, $parse_str) {
   
$parse_info = unpack("x$bytes_parsed/cstr_len", $parse_str);
   
$str_len = $parse_info["str_len"];
   
$bytes_parsed = $bytes_parsed + 1;
   
$parse_info = unpack("x$bytes_parsed/A".$str_len."str", $parse_str);
   
$str = $parse_info["str"];
   
$bytes_parsed = $bytes_parsed + strlen($str);

    return array(
$str, $bytes_parsed);
}

?>



PHP Powered Diese Seite bei php.net
The PHP manual text and comments are covered by the Creative Commons Attribution 3.0 License © the PHP Documentation Group - Impressum - mail("TO:Reinhard Neidl",...)