PHP Conference Japan 2024

unpack

(PHP 4, PHP 5, PHP 7, PHP 8)

unpack從二進位字串解開資料

說明

unpack(string $format, string $string, int $offset = 0): array|false

根據給定的 format,從二進位字串解開資料到陣列中。

解開的資料會儲存在關聯陣列中。為了達到這個目的,您必須為不同的格式代碼命名,並以斜線 / 分隔它們。如果存在重複器引數,則每個陣列索引鍵都會在給定的名稱後面加上一個序號。

進行了變更以使此函式與 Perl 一致

  • 「a」代碼現在會保留尾隨的 NULL 位元組。
  • 「A」代碼現在會移除所有尾隨的 ASCII 空白(空格、定位點、換行符號、歸位符號和 NULL 位元組)。
  • 新增「Z」代碼用於以 NULL 填補的字串,並移除尾隨的 NULL 位元組。

參數

format

請參閱 pack() 以取得格式代碼的說明。

string

已封裝的資料。

offset

開始解開資料的偏移量。

傳回值

傳回一個關聯陣列,其中包含二進位字串的解開元素,失敗時則傳回 false

更新日誌

版本 說明
7.2.0 floatdouble 類型同時支援大端和小端。
7.1.0 新增了選用的 offset

範例

範例 #1 unpack() 範例

<?php
$binarydata
= "\x04\x00\xa0\x00";
$array = unpack("cchars/nint", $binarydata);
print_r($array);
?>

上面的範例將會輸出

Array
(
    [chars] => 4
    [int] => 160
)

範例 #2 unpack() 範例,使用重複器

<?php
$binarydata
= "\x04\x00\xa0\x00";
$array = unpack("c2chars/nint", $binarydata);
print_r($array);
?>

上面的範例將會輸出

Array
(
    [chars1] => 4
    [chars2] => 0
    [int] => 40960
)

注意

警告

請注意,PHP 在內部將整數值儲存為帶符號的。如果您解開一個大型的無符號長整數,而它的大小與 PHP 內部儲存的值相同,即使指定了無符號解開,結果仍會是一個負數。

警告

如果您未命名元素,則會使用從 1 開始的數值索引。請注意,如果您有多個未命名的元素,則某些資料會被覆寫,因為每個元素的編號都會從 1 重新開始。

範例 #3 unpack() 範例,使用未命名的索引鍵

<?php
$binarydata
= "\x32\x42\x00\xa0";
$array = unpack("c2/n", $binarydata);
var_dump($array);
?>

上面的範例將會輸出

array(2) {
  [1]=>
  int(160)
  [2]=>
  int(66)
}

請注意,來自 c 指定符的第一個值會被來自 n 指定符的第一個值覆寫。

參見

  • pack() - 將資料封裝成二進位字串

新增註解

使用者貢獻的註解 14 則註解

21
stanislav dot eckert at vizson dot de
8 年前
一個輔助類別,用於將整數轉換為二進位字串,反之亦然。對於讀取和寫入整數到/從檔案或 Socket 非常有用。

<?php

class int_helper
{
public static function
int8($i) {
return
is_int($i) ? pack("c", $i) : unpack("c", $i)[1];
}

public static function
uInt8($i) {
return
is_int($i) ? pack("C", $i) : unpack("C", $i)[1];
}

public static function
int16($i) {
return
is_int($i) ? pack("s", $i) : unpack("s", $i)[1];
}

public static function
uInt16($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";

if (
$endianness === true) { // big-endian (大端)
$i = $f("n", $i);
}
else if (
$endianness === false) { // little-endian (小端)
$i = $f("v", $i);
}
else if (
$endianness === null) { // machine byte order (機器位元組順序)
$i = $f("S", $i);
}

return
is_array($i) ? $i[1] : $i;
}

public static function
int32($i) {
return
is_int($i) ? pack("l", $i) : unpack("l", $i)[1];
}

public static function
uInt32($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";

if (
$endianness === true) { // big-endian (大端)
$i = $f("N", $i);
}
else if (
$endianness === false) { // little-endian (小端)
$i = $f("V", $i);
}
else if (
$endianness === null) { // machine byte order (機器位元組順序)
$i = $f("L", $i);
}

return
is_array($i) ? $i[1] : $i;
}

public static function
int64($i) {
return
is_int($i) ? pack("q", $i) : unpack("q", $i)[1];
}

public static function
uInt64($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";

if (
$endianness === true) { // big-endian (大端)
$i = $f("J", $i);
}
else if (
$endianness === false) { // little-endian (小端)
$i = $f("P", $i);
}
else if (
$endianness === null) { // machine byte order (機器位元組順序)
$i = $f("Q", $i);
}

return
is_array($i) ? $i[1] : $i;
}
}
?>

使用範例
<?php
Header
("Content-Type: text/plain");
include(
"int_helper.php");

echo
int_helper::uInt8(0x6b) . PHP_EOL; // k
echo int_helper::uInt8(107) . PHP_EOL; // k
echo int_helper::uInt8("\x6b") . PHP_EOL . PHP_EOL; // 107

echo int_helper::uInt16(4101) . PHP_EOL; // \x05\x10
echo int_helper::uInt16("\x05\x10") . PHP_EOL; // 4101
echo int_helper::uInt16("\x05\x10", true) . PHP_EOL . PHP_EOL; // 1296

echo int_helper::uInt32(2147483647) . PHP_EOL; // \xff\xff\xff\x7f
echo int_helper::uInt32("\xff\xff\xff\x7f") . PHP_EOL . PHP_EOL; // 2147483647

// 注意:請在 PHP 的 64 位元版本上測試此程式碼
echo int_helper::uInt64(9223372036854775807) . PHP_EOL; // \xff\xff\xff\xff\xff\xff\xff\x7f
echo int_helper::uInt64("\xff\xff\xff\xff\xff\xff\xff\x7f") . PHP_EOL . PHP_EOL; // 9223372036854775807

?>
15
Sergio Santana: ssantana at tlaloc dot imta dot mx
20 年前
這是關於我先前文章的最後一個範例。為了清楚起見,我再次在此處包含該範例,該範例擴展了正式文件中提供的範例

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

這會輸出

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

在這裡,我們假設字元 'A' 的 ASCII 碼是十進位的 65。

請記住,格式字串的結構是
<格式碼> [<計數>] [<陣列鍵>] [/ ...],
在此範例中,格式字串指示函式執行以下操作
1. ("c2...") 從第二個參數 ("AA ...") 讀取兩個字元,
2. (...chars...) 為這兩個讀取的字元使用陣列鍵 "chars1" 和 "chars2",
這些讀取的兩個字元,
3. (.../n...) 從第二個參數 (...\0A") 讀取一個短整數,
4. (...int") 使用 "int" 這個詞作為剛讀取到的短整數的陣列鍵。
簡短。

我希望現在更清楚了,

Sergio。
11
jjfoerch at earthlink dot net
20 年前
我遇到一種情況,必須解壓縮一個檔案,其中填充了以小端序排列的雙精度浮點數,而且這種方式必須能在小端序或大端序的機器上運作。PHP 沒有任何格式化程式碼可以變更雙精度浮點數的位元組順序,所以我寫了這個解決方案。

<?php
/*以下程式碼是針對 PHP 的 unpack 函式所提供的解決方案,
該函式無法解壓縮以目前機器位元組順序相反的方式封裝的雙精度
浮點數。
*/
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';
?>

這個解決方案的使用方式如下

<?php

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

?>

在小端序機器上,$unpack_workaround 將直接指向 unpack 函式。在大端序機器上,它將呼叫解決方案函式。

請注意,此解決方案僅適用於雙精度浮點數。在我的專案中,我不需要檢查單精度浮點數。
8
kennwhite dot nospam at hotmail dot com
20 年前
如果使用以零為基礎的索引很有用/必要,那麼與其使用

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

不如嘗試

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

這將會傳回一個以 0 為基礎的陣列

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

而不是在沒有提供索引鍵的情況下,從 unpack 傳回的預設以 1 為基礎的陣列

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

不常使用,但是只有一個參數的 array_merge() 會壓縮依序排列的數字索引,從索引 [0] 開始。
2
Anonymous
15 年前
我在處理固定寬度檔案時發現有用的函式,與 unpack/pack 函式相關。
<?php
/**
* funpack
* format: 索引鍵與長度配對的陣列
* data: 要解壓縮的字串
*/
function funpack($format, $data){
foreach (
$format as $key => $len) {
$result[$key] = trim(substr($data, $pos, $len));
$pos+= $len;
}
return
$result;
}

/**
* fpack
* format: 索引鍵與長度配對的陣列
* data: 要封裝之索引鍵與數值配對的陣列
* pad: 填補方向
*/
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;
}
?>
3
Sergio Santana: ssantana at tlaloc dot imta dot mx
20 年前
假設我們需要取得某種整數的內部表示法,例如 65,作為四位元組長度。然後我們使用類似這樣的東西

<?
$i = 65;
$s = pack("l", $i); // long 32 位元,機器位元組順序
echo strlen($s) . "<br>\n";
echo "***$s***<br>\n";
?>

輸出為

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

4
***A***

(也就是字串 "A\0\0\0")

現在我們要從字串 "A\0\0\0" 返回數字 65。在這種情況下,我們可以使用

<?
$s = "A\0\0\0"; // 這個字串是數字 65 的位元組表示法
$arr = unpack("l", $s);
foreach ($arr as $key => $value)
echo "\$arr[$key] = $value<br>\n";
?>

而這個會輸出
X-Powered-By: PHP/4.1.2
Content-type: text/html

$arr[] = 65

讓我們為陣列索引鍵命名,例如 "mykey"。在這種情況下,我們可以使用

<?
$s = "A\0\0\0"; // 這個字串是數字 65 的位元組表示法
$arr = unpack("lmykey", $s);
foreach ($arr as $key => $value)
echo "\$arr[$key] = $value\n";
?>

而這個會輸出
X-Powered-By: PHP/4.1.2
Content-type: text/html

$arr[mykey] = 65

"unpack" 文件有點讓人困惑。我認為一個更完整的範例可以是

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

其輸出為

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

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

請注意,格式字串類似於
<格式代碼> [<計數>] [<陣列索引鍵>] [/ ...]

我希望這能釐清一些事情

Sergio
1
ludwig at kni-online dot de
4 年前
在解壓縮之前,別忘了先解碼使用者定義的虛擬位元組序列...
<?php
$byte_code_string
= '00004040';
var_dump ( unpack ( 'f', $byte_code_string ) );
?>
結果
array(1) {
[1]=>
float(6.4096905560973E-10)
}

然而
<?php
$byte_code_string
= '00004040';
var_dump ( unpack ( 'f', hex2bin ( $byte_code_string ) ) );
?>
結果
array(1) {
[1]=>
float(3)
}
0
Aaron Wells
14 年前
將二進位資料轉換為 PHP 資料類型的另一個選項是使用 Zend Framework 的 Zend_Io_Reader 類別
http://bit.ly/9zAhgz

還有一個 Zend_Io_Writer 類別可以執行相反的操作。
-1
Anonymous Coward
16 年前
警告:這個 unpack 函式會建立一個索引鍵從 1 而不是從 0 開始的陣列。

例如
<?php
function read_field($h) {
$a=unpack("V",fread($h,4));
return
fread($h,$a[1]);
}
?>
-3
rogier
13 年前
請注意您的 PHP 所在的系統的行為。

在 x86 上,unpack 可能不會產生您預期的 UInt32 結果

這是因為 PHP 的內部特性,整數在內部是以帶號 (SIGNED) 的形式儲存!

對於 x86 系統,unpack('N', "\xff\xff\xff\xff") 的結果會是 -1
對於(大多數?)x64 系統,unpack('N', "\xff\xff\xff\xff") 的結果會是 4294967295。

可以透過檢查 PHP_INT_SIZE 的值來驗證這一點。
如果這個值是 4,表示您的 PHP 在內部儲存的是 32 位元。
值為 8 則表示內部儲存的是 64 位元。

為了規避這個「問題」,您可以使用以下程式碼來避免 unpack 的問題。
這段程式碼適用於大端位元組順序 (big endian),但可以輕鬆調整為小端位元組順序 (little endian) (同樣地,類似的程式碼也適用於 64 位元整數)

<?php
function _uint32be($bin)
{
// $bin 是表示整數的 32 位元大端位元組順序二進制字串
if (PHP_INT_SIZE <= 4){
list(,
$h,$l) = unpack('n*', $bin);
return (
$l + ($h*0x010000));
}
else{
list(,
$int) = unpack('N', $bin);
return
$int;
}
}
?>

請注意,您*也可以*使用 sprintf('%u', $x) 來顯示無號的實際值。
另請注意,(至少當 PHP_INT_SIZE = 4 時),當輸入大於 0x7fffffff 時,結果將會是 float 值 (可以用 gettype 來檢查);

希望這對大家有幫助。
-2
norwood at computer dot org
14 年前
從 Excel 試算表讀取文字儲存格時,會傳回一個內嵌低位元組 Null 字元的字串:0x4100 0x4200 等。為了移除 Null 字元,使用了

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

(unpack() 在這裡似乎沒有太大幫助;需要字元來重新構成字串,而不是整數。)
-4
googlybash24 at aol dot com
12 年前
若要將大端位元組順序轉換為小端位元組順序,或將小端位元組順序轉換為大端位元組順序,請使用以下範例方法

<?php
// file_get_contents() 會傳回二進制值,unpack("V*", _ ) 會傳回一個 32 位元小端位元組順序的無號長整數十進制值,但是在那之後單獨使用 bin2hex() 只會提供檔案中的十六進制資料,因此我們改為使用:
// file_get_contents()、unpack("V*", _ ),然後依序使用 dechex(),以達到位元組交換的效果。
?>

透過此範例中的方法邏輯,您可以發現如何根據需要交換位元組順序。
-3
sica at wnet com br
15 年前
以下腳本示範如何將多個值儲存到檔案中,並以 "\r\n" 分隔,以及如何恢復這些值。

<?php
// 將兩個整數值儲存到二進制檔案中
$nomearq = "./teste.bin";
$valor = 123;
$ptrarq = fopen($nomearq, "wb");
$valorBin = pack("L",$valor);
echo
"第一個值 ($valor) 使用 ";
echo
fwrite($ptrarq, $valorBin)." 位元組封裝<br>";
echo
"分隔符號 \\r\\n 使用 ";
echo
fwrite($ptrarq, "\r\n")." 位元組<br>";
$valor = 456;
$valorBin = pack("L",$valor);
echo
"第二個值 ($valor) 使用 ";
echo
fwrite($ptrarq, $valorBin)." 位元組封裝<br>";
fclose($ptrarq);

// 恢復已儲存的值
$ptrarq = fopen($nomearq, "rb");
$valorBin = file($nomearq,filesize($nomearq));
echo
"<br>讀取的值是:<br>";
foreach(
$valorBin as $valor){
$valor = unpack("L",$valor);
print_r($valor);
echo
"<br>";
}
fclose($ptrarq);
?>

結果
第一個值 (123) 使用 4 位元組封裝
分隔符號 \r\n 使用 2 位元組
第二個值 (456) 使用 4 位元組封裝

讀取的值是:
Array ( [1] => 123 )
Array ( [1] => 456 )
-3
iredden at redden dot on dot ca
24 年前
<?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);
}

?>
To Top