標題:「二進位」和「文字」檔案之間的區別

發表:真實

描述:探索「二進位」和「文字」檔案之間的差異。

標籤: 二進位, 文字, 編碼, unix


本文探討「二進位」和「文本」文件的主題。兩者有什麼區別(如果有的話)?對於「二進位」或「文字」檔案的構成是否有明確的定義?

我們從兩個候選文件開始我們的旅程,我們將其內容直觀地分別分類為「文字」和「二進位」資料:

echo "hello 🌍" > message
convert -size 1x1 xc:white png:white

我們建立了兩個檔案:一個名為message的文件,其中包含文字內容「hello 🌍」 (包括 Unicode 符號「Earth Globe Europe-Africa」 ),以及一個帶有名為white的單一白色像素的PNG 圖像。檔案副檔名被故意省略。

為了證明某些程式區分「文字」和「二進位」文件,請查看grep如何更改其行為:

▶ grep -R hello            
message:hello 🌍

▶ grep -R PNG
Binary file white matches

diff做了類似的事情:

▶ echo "hello world" > other-message
▶ diff other-message message 
1c1
< hello world
---
> hello 🌍

▶ convert -size 1x1 xc:black png:black
▶ diff black white
Binary files black and white differ

這些程式如何區分「文字」和「二進位」檔案?

在回答這個問題之前,讓我們先試著給一個定義。顯然,在基本檔案系統層級上,每個檔案只是位元組的集合,因此可以被視為二進位資料。另一方面,「文字」和「非文字」(以下簡稱「二進位」)資料之間的差異似乎對grepdiff之類的程式很有幫助,只要不弄亂終端模擬器的輸出即可。

所以也許我們可以從定義「文字」資料開始。從將文字作為Unicode 程式碼點序列的抽象概念開始似乎是合理的。程式碼點的範例包括käא等字符,以及%🙈等特殊符號。要將給定的文字儲存為位元組序列,我們需要選擇一種編碼。如果我們希望能夠表示整個 Unicode 範圍,我們通常會選擇 UTF-8,有時會選擇 UTF-16 或 UTF-32。從歷史上看,僅支援當今 Unicode 一部分的編碼也很重要。最突出的是 US-ASCII 和 Latin1 (ISO 8859-1),但還有更多。所有這些在字節級別上看起來都不同。

僅給出文件的內容(而不是其建立方式的歷史記錄),因此我們可以嘗試以下定義:

如果檔案的內容由 Unicode 程式碼點的編碼序列組成,則該檔案稱為「文字檔案」。

這個定義有兩個實際問題。首先,我們需要所有可能的編碼的清單。其次,為了測試文件的內容是否以給定的編碼進行編碼,我們必須解碼文件的全部內容並查看是否成功。整個過程會非常緩慢。

事實證明,有一種更快的方法來區分文字和二進位文件,但它是以精確度為代價的。

要了解其工作原理,讓我們回到兩個候選文件並探索它們的位元組級內容。我使用hexyl作為十六進位檢視器,但您也可以使用hexdump -C

“message”和“white”的二進位內容

請注意,這兩個檔案都包含 ASCII 範圍 ( 007f ) 以內和之外的位元組。例如, message檔案中的四個位元組f0 9f 8c 8d是 Unicode 程式碼點U+1F30D (🌍) 的 UTF-8 編碼版本。另一方面, white圖像開頭的位元組50 4e 47是字元PNG ² 的簡單 ASCII 編碼版本。

很明顯,查看 ASCII 範圍之外的位元組不能用作檢測「二進位」檔案的方法。但是,這兩個文件之間存在差異。圖像檔案包含大量 NULL 位元組 ( 00 ),而短文字訊息則不包含。事實證明,這可以變成一種簡單的啟發式方法來檢測二進位文件,因為許多編碼文字資料不包含任何 NULL 位元組(即使它可能是合法的)。

事實上,這正是diffgrep用來偵測「二進位」檔案的方法。以下巨集包含在diff的原始碼 ( src/io.c )中:

#define binary_file_p(buf, size) (memchr (buf, 0, size) != 0)

這裡, memchr(const void *s, int c, size_t n)函數用於搜尋從buf開始的記憶體區域的初始size字節,以查找字元0 。為了進一步加快此過程,通常僅將檔案的前幾個位元組讀入緩衝區buf (例如 1024 位元組)。總而言之, grepdiff使用以下啟發式方法:

如果檔案內容的前 1024 位元組不包含任何 NULL 位元組,則該檔案很可能是「文字檔案」。

請注意,有一些失敗的反例。例如,即使不太可能,UTF-8 編碼的文字也可以合法地包含 NULL 位元組。相反,某些特定的二進位格式(例如二進位PGM )不包含 NULL 位元組。此方法通常還將 UTF-16 和 UTF-32 編碼文字分類為“二進位”,因為它們使用 NULL 位元組對常見的 Latin-1 程式碼點進行編碼:

▶ iconv -f UTF-8 -t UTF-16 message > message-utf16
▶ hexdump -C message-utf16 
00000000  ff fe 68 00 65 00 6c 00  6c 00 6f 00 20 00 3c d8  |..h.e.l.l.o. .<.|
00000010  0d df 0a 00                                       |....|
00000014
▶ grep . message-utf16                            
Binary file message-utf16 matches

然而,這種啟發式方法非常有用。我用 Rust 編寫了一個小型庫,它使用此方法的稍微改進的版本來快速確定給定檔案是否包含「二進位」或「文字」資料。它在我的程式bat中用於防止“二進位”檔案轉儲到終端:

bat,偵測二進位文件

註腳

1 請注意,有些編碼會在檔案開頭寫入所謂的位元組順序標記(BOM),以指示編碼類型。例如,UTF-32 的小尾數變體使用ff fe 00 00 。這些 BOM 將有助於解決第二點,因為我們不需要解碼檔案的全部內容。不幸的是,加入 BOM 是可選的,並且許多編碼都沒有指定 BOM。

² 50 4e 47是 PNG 格式的幻數的一部份。幻數與 BOM 類似,許多二進位格式在檔案開頭使用幻數來表示其類型。使用幻數來檢測某些類型的「二進位」檔案是file工具使用的一種方法。


原文出處:https://dev.to/sharkdp/what-is-a-binary-file-2cf5


共有 0 則留言