
我想計算某個特定位元組序列在我擁有的檔案中發生的次數。例如,我想找出該數字\0xdeadbeef
在可執行檔中出現了多少次。現在我正在使用 grep 執行此操作:
#/usr/bin/fish
grep -c \Xef\Xbe\Xad\Xde my_executable_file
(位元組以相反的順序寫入,因為我的CPU是little-endian)
但是,我的方法有兩個問題:
- 這些
\Xnn
轉義序列僅在魚殼中起作用。 - grep 實際上是在計算包含我的幻數的行數。如果該模式在同一行中出現兩次,則僅計算一次。
有辦法解決這些問題嗎?如何讓這一行在 Bash shell 中運作並準確計算該模式在文件內出現的次數?
答案1
這是所要求的單行解決方案(對於最近具有“進程替換”的 shell):
grep -o "ef be ad de" <(hexdump -v -e '/1 "%02x "' infile.bin) | wc -l
如果沒有可用的「進程替換」<(…)
,只需使用 grep 作為過濾器:
hexdump -v -e '/1 "%02x "' infile.bin | grep -o "ef be ad de" | wc -l
以下是該解決方案各部分的詳細說明。
來自十六進制數字的位元組值:
你的第一個問題很容易解決:
這些 \Xnn 轉義序列僅在 Fish shell 中有效。
將上部變更X
為下部x
並使用 printf (對於大多數 shell):
$ printf -- '\xef\xbe\xad\xde'
或使用:
$ /usr/bin/printf -- '\xef\xbe\xad\xde'
對於那些選擇不實作 '\x' 表示的 shell。
當然,將十六進制轉換為八進制將適用於(幾乎)任何 shell:
$ "$sh" -c 'printf '\''%b'\'' "$(printf '\''\\0%o'\'' $((0xef)) $((0xbe)) $((0xad)) $((0xde)) )"'
其中“$sh”是任何(合理的)shell。但要正確引用它是相當困難的。
二進位檔案。
0x0A
最可靠的解決方案是將檔案和位元組序列(兩者)轉換為某種編碼,這種編碼對於奇數字符值(如 (new line)或 (null byte) )沒有問題0x00
。使用設計和適應處理「文字檔案」的工具來正確管理兩者都相當困難。
像base64 這樣的轉換可能看起來是有效的,但它提出了一個問題,即每個輸入位元組可能有最多三個輸出表示,這取決於它是mod 24(位元)位置的第一個、第二個還是第三個位元組。
$ echo "abc" | base64
YWJjCg==
$ echo "-abc" | base64
LWFiYwo=
$ echo "--abc" | base64
LS1hYmMK
$ echo "---abc" | base64 # Note that YWJj repeats.
LS0tYWJjCg==
十六進制變換。
這就是為什麼最強大的轉換應該是從每個位元組邊界開始的轉換,就像簡單的十六進位表示一樣。
我們可以使用以下任何工具來取得具有檔案十六進位表示形式的檔案:
$ od -vAn -tx1 infile.bin | tr -d '\n' > infile.hex
$ hexdump -v -e '/1 "%02x "' infile.bin > infile.hex
$ xxd -c1 -p infile.bin | tr '\n' ' ' > infile.hex
在這種情況下,要搜尋的位元組序列已經是十六進位。
:
$ var="ef be ad de"
但它也可以轉變。往返十六進制-二進制-十六進制的範例如下:
$ echo "ef be ad de" | xxd -p -r | od -vAn -tx1
ef be ad de
搜尋字串可以根據二進位表示來設定。上面提供的三個選項中的任何一個 od、hexdump 或 xxd 都是等效的。只需確保包含空格以確保匹配位於位元組邊界上(不允許半位元組移位):
$ a="$(printf "\xef\xbe\xad\xde" | hexdump -v -e '/1 "%02x "')"
$ echo "$a"
ef be ad de
如果二進位檔案如下所示:
$ cat infile.bin | xxd
00000000: 5468 6973 2069 7320 efbe adde 2061 2074 This is .... a t
00000010: 6573 7420 0aef bead de0a 6f66 2069 6e70 est ......of inp
00000020: 7574 200a dead beef 0a66 726f 6d20 6120 ut ......from a
00000030: 6269 0a6e 6172 7920 6669 6c65 2e0a 3131 bi.nary file..11
00000040: 3232 3131 3232 3131 3232 3131 3232 3131 2211221122112211
00000050: 3232 3131 3232 3131 3232 3131 3232 3131 2211221122112211
00000060: 3232 0a
然後,簡單的 grep 搜尋將給出匹配序列的列表:
$ grep -o "$a" infile.hex | wc -l
2
一條線?
這一切都可以在一行中執行:
$ grep -o "ef be ad de" <(xxd -c 1 -p infile.bin | tr '\n' ' ') | wc -l
例如,11221122
在同一個文件中搜尋將需要以下兩個步驟:
$ a="$(printf '11221122' | hexdump -v -e '/1 "%02x "')"
$ grep -o "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ') | wc -l
4
要「查看」比賽:
$ grep -o "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ')
3131323231313232
3131323231313232
3131323231313232
3131323231313232
$ grep "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ')
… 0a3131323231313232313132323131323231313232313132323131323231313232313132320a
緩衝
人們擔心 grep 會緩衝整個文件,如果文件很大,會給電腦帶來沉重的負載。為此,我們可以使用無緩衝的 sed 解決方案:
a='ef be ad de'
hexdump -v -e '/1 "%02x "' infile.bin |
sed -ue 's/\('"$a"'\)/\n\1\n/g' |
sed -n '/^'"$a"'$/p' |
wc -l
第一個 sed 是無緩衝的 ( -u
),僅用於在每個符合字串的流上註入兩個換行符。第二個sed
只會列印(短)匹配行。 wc -l 將計算匹配的行數。
這只會緩衝一些短行。第二個 sed 中的匹配字串。這所使用的資源應該相當低。
或者,理解起來有些複雜,但在一個 sed 中具有相同的想法:
a='ef be ad de'
hexdump -v -e '/1 "%02x "' infile.bin |
sed -u '/\n/P;//!s/'"$a"'/\n&\n/;D' |
wc -l
答案2
使用 GNUgrep
的-P
(perl-regexp) 標誌
LC_ALL=C grep -oaP '\xef\xbe\xad\xde' file | wc -l
LC_ALL=C
是為了避免多字節語言環境中出現問題,grep
否則會嘗試將位元組序列解釋為字元。
-a
將二進位檔案視為等同於文字檔案(而不是正常行為,其中grep
僅列印出是否至少有一個匹配項)
答案3
答案4
使用 GNU awk
,您可以執行以下操作:
LC_ALL=C awk -v 'RS=\xef\xbe\xad\xde' 'END{print NR - (NR && RT == "")}'
如果任何位元組是 ERE 運算符,則必須對它們進行轉義(使用\\
)。就像0x2e
which is.
必須輸入為\\.
or \\\x2e
。除此之外,它應該適用於任意位元組值,包括 0 和 0xa。
請注意,這並不只是NR-1
因為有一些特殊情況那麼簡單:
- 當輸入為空時,NR為0,NR-1將給出-1。
- 當輸入以記錄分隔符號結束時,此後不會建立空記錄。我們用 來測試這一點
RT==""
。
另請注意,在最壞的情況下(如果檔案不包含搜尋字詞),檔案最終將被整個載入到記憶體中)。