Làm thế nào tôi có thể đếm số lần một chuỗi byte xảy ra trong một tệp?


16

Tôi muốn đếm số lần một chuỗi byte nhất định xảy ra trong một tệp mà tôi có. Ví dụ, tôi muốn tìm hiểu số lần \0xdeadbeefxuất hiện trong một tệp thực thi. Ngay bây giờ tôi đang làm điều đó bằng cách sử dụng grep:

#/usr/bin/fish
grep -c \Xef\Xbe\Xad\Xde my_executable_file

(Các byte được viết theo thứ tự ngược lại vì CPU của tôi là endian nhỏ)

Tuy nhiên, tôi có hai vấn đề với cách tiếp cận của mình:

  • Những \Xnnchuỗi thoát chỉ hoạt động trong vỏ cá.
  • grep thực sự đang đếm số dòng có chứa số ma thuật của tôi. Nếu mô hình xảy ra hai lần trong cùng một dòng, nó sẽ chỉ được tính một lần.

Có cách nào để khắc phục những vấn đề này? Làm cách nào tôi có thể làm cho một lớp lót này chạy trong shell Bash và đếm chính xác số lần mẫu xuất hiện trong tệp?


một số trợ giúp: unix.stackexchange.com/q/231213/117549 - cụ thể,grep -o
Jeff Schaller

1
grep là công cụ sai để sử dụng. Hãy xem xét bgrep hoặc bgrep2.
fpmurphy

3
Nếu trình tự tìm kiếm là 11221122, cái gì sẽ được trả về trên đầu vào như thế 112211221122nào? 1 hoặc 2?
Stéphane Chazelas

Tôi sẽ ổn khi báo cáo 2 hoặc 3 trận đấu trong trường hợp đó. Bất cứ điều gì sẽ đơn giản hơn để thực hiện.
hugomg

Câu trả lời:


15

Đây là giải pháp một lớp lót được yêu cầu (đối với các shell gần đây có "thay thế quá trình"):

grep -o "ef be ad de" <(hexdump -v -e '/1 "%02x "' infile.bin) | wc -l

Nếu không có "thay thế quy trình" <(…), chỉ cần sử dụng grep làm bộ lọc:

hexdump -v -e '/1 "%02x "' infile.bin  | grep -o "ef be ad de" | wc -l

Dưới đây là mô tả chi tiết của từng phần của giải pháp.

Giá trị byte từ số hex:

Vấn đề đầu tiên của bạn rất dễ giải quyết:

Các chuỗi thoát \ Xnn chỉ hoạt động trong vỏ cá.

Thay đổi phần trên Xthành phần dưới xvà sử dụng printf (đối với hầu hết các shell):

$ printf -- '\xef\xbe\xad\xde'

Hoặc dùng:

$ /usr/bin/printf -- '\xef\xbe\xad\xde'

Đối với những shell đó chọn không triển khai biểu diễn '\ x'.

Tất nhiên, dịch hex sang bát phân sẽ hoạt động trên (gần như) bất kỳ shell nào:

$ "$sh" -c 'printf '\''%b'\'' "$(printf '\''\\0%o'\'' $((0xef)) $((0xbe)) $((0xad)) $((0xde)) )"'

Trong đó "$ sh" là bất kỳ vỏ (hợp lý) nào. Nhưng nó là khá khó khăn để giữ cho nó được trích dẫn chính xác.

Tệp nhị phân.

Giải pháp mạnh mẽ nhất là chuyển đổi tệp và chuỗi byte (cả hai) thành một số mã hóa không có vấn đề với các giá trị ký tự lẻ như (dòng mới) 0x0Ahoặc (byte byte) 0x00. Cả hai đều khá khó để quản lý chính xác với các công cụ được thiết kế và điều chỉnh để xử lý "tệp văn bản".

Một phép biến đổi như base64 có vẻ hợp lệ, nhưng nó thể hiện một vấn đề là mỗi byte đầu vào có thể có tối đa ba biểu diễn đầu ra tùy thuộc vào đó là byte thứ nhất, thứ hai hay thứ ba của vị trí mod 24 (bit).

$ echo "abc" | base64
YWJjCg==

$ echo "-abc" | base64
LWFiYwo=

$ echo "--abc" | base64
LS1hYmMK

$ echo "---abc" | base64        # Note that YWJj repeats.
LS0tYWJjCg==

Biến đổi hex.

Đó là lý do tại sao phép biến đổi mạnh nhất phải là một phép bắt đầu trên mỗi ranh giới byte, giống như biểu diễn HEX đơn giản.
Chúng tôi có thể nhận được một tệp có biểu diễn hex của tệp bằng bất kỳ công cụ nào trong số này:

$ 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

Chuỗi byte để tìm kiếm đã ở dạng hex trong trường hợp này.
:

$ var="ef be ad de"

Nhưng nó cũng có thể được chuyển đổi. Một ví dụ về hex-bin-hex khứ hồi sau:

$ echo "ef be ad de" | xxd -p -r | od -vAn -tx1
ef be ad de

Chuỗi tìm kiếm có thể được đặt từ biểu diễn nhị phân. Bất kỳ tùy chọn nào trong ba tùy chọn được trình bày ở trên od, hexdump hoặc xxd đều tương đương. Chỉ cần đảm bảo bao gồm các khoảng trắng để đảm bảo khớp trên ranh giới byte (không cho phép dịch chuyển nibble):

$ a="$(printf "\xef\xbe\xad\xde" | hexdump -v -e '/1 "%02x "')"
$ echo "$a"
ef be ad de

Nếu tệp nhị phân trông như thế này:

$ 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

Sau đó, một tìm kiếm grep đơn giản sẽ đưa ra danh sách các chuỗi phù hợp:

$ grep -o "$a" infile.hex | wc -l
2

Một đường thẳng?

Tất cả có thể được thực hiện trong một dòng:

$ grep -o "ef be ad de" <(xxd -c 1 -p infile.bin | tr '\n' ' ') | wc -l

Ví dụ: tìm kiếm 11221122trong cùng một tệp sẽ cần hai bước sau:

$ a="$(printf '11221122' | hexdump -v -e '/1 "%02x "')"
$ grep -o "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ') | wc -l
4

Để "xem" các trận đấu:

$ grep -o "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ')
3131323231313232
3131323231313232
3131323231313232
3131323231313232

$ grep "$a" <(xxd -c1 -p infile.bin | tr '\n' ' ')

Cẩu 0a 3131323231313232313132323131323231313232313132323131323231313232 313132320a


Bộ đệm

Có một lo ngại rằng grep sẽ đệm toàn bộ tệp và nếu tệp lớn, sẽ tạo ra tải nặng cho máy tính. Vì vậy, chúng tôi có thể sử dụng một giải pháp sed không có bộ đệm:

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 tiên không có bộ đệm ( -u) và chỉ được sử dụng để thêm hai dòng mới trên luồng trên mỗi chuỗi khớp. Thứ hai sedsẽ chỉ in các dòng phù hợp (ngắn). Wc -l sẽ đếm các dòng khớp.

Điều này sẽ chỉ đệm một số dòng ngắn. Chuỗi phù hợp (s) trong sed thứ hai. Điều này nên khá thấp trong tài nguyên được sử dụng.

Hoặc, hơi phức tạp hơn để hiểu, nhưng cùng một ý tưởng trong một 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
Lưu ý rằng nếu bạn đặt tất cả văn bản trên một dòng, điều đó có nghĩa là grepcuối cùng sẽ tải toàn bộ trong bộ nhớ (ở đây gấp đôi kích thước của tệp gốc + 1 vì mã hóa hex), vì vậy cuối cùng, nó sẽ kết thúc nhiều hơn trên cao hơn pythoncách tiếp cận hoặc perlmột với -0777. Bạn cũng cần một greptriển khai hỗ trợ các dòng có độ dài tùy ý (những dòng hỗ trợ -othường làm). Tốt trả lời khác.
Stéphane Chazelas

1
Các phiên bản hex của bạn khớp với các giá trị nibble-shift? E fb e dd e? ngoài các byte mong muốn. od -An -tx1 | tr -d '\n'hoặc hexdump -v -e '/1 " %02x"'với một chuỗi tìm kiếm cũng chứa khoảng trắng tránh điều này, nhưng tôi thấy không có cách khắc phục nào như vậy xxd.
dave_thndry_085

@ dave_thndry_085 Trả lời chỉnh sửa. Tôi tin rằng câu trả lời sẽ chỉ khớp với các ranh giới byte ngay bây giờ, Cảm ơn một lần nữa.
sorontar

@ StéphaneChazelas Bạn có thể xem lại tùy chọn đề xuất sử dụng một chiếc sed không có bộ đệm. Cảm ơn.
sorontar

sed -u(nếu có) là dành cho unbuffer. Điều đó có nghĩa là nó sẽ đọc từng byte một lần vào đầu vào và xuất ngay đầu ra của nó mà không cần đệm. Trong mọi trường hợp, nó vẫn sẽ cần tải toàn bộ dòng trong không gian mẫu, vì vậy sẽ không giúp đỡ ở đây.
Stéphane Chazelas

7

Với GNU grep's -P(perl-regexp) cờ

LC_ALL=C grep -oaP '\xef\xbe\xad\xde' file | wc -l

LC_ALL=Clà để tránh các vấn đề trong các địa phương nhiều byte, grepnếu không thì sẽ cố gắng diễn giải các chuỗi byte dưới dạng các ký tự.

-axử lý các tệp nhị phân tương đương với tệp văn bản (thay vì hành vi thông thường, trong đó grepchỉ in ra xem có ít nhất một kết quả khớp hay không)


Giải pháp này luôn cho tôi 0 trận đấu thay vì số chính xác.
hugomg

@hugomg, có thể là bạn cần đảo ngược các byte được truyền để grep làm cho nó khớp?
iruvar

Tôi không nghĩ đó là thứ tự. Hai câu trả lời khác cho câu hỏi này hoạt động chính xác.
hugomg

2
@hugomg, đó là miền địa phương. Xem chỉnh sửa.
Stéphane Chazelas

2
Tôi sẽ đề nghị bao gồm -atùy chọn, nếu không grep sẽ trả lời với Binary file file.bin matchesbất kỳ tệp nào mà grep phát hiện là nhị phân.
sorontar

6
PERLIO=:raw perl -nE '$c++ while m/\xef\xbe\xad\xde/g; END{say $c}' file

Cái nào coi (các) tệp đầu vào là nhị phân (không có bản dịch cho các dòng hoặc mã hóa, xem perlrun ) sau đó lặp lại (các) tệp đầu vào không in tăng bộ đếm cho tất cả các kết quả khớp của hex (hoặc bất kỳ dạng nào, xem perlre ) .


2
Lưu ý rằng bạn không thể sử dụng nếu chuỗi tìm kiếm chứa byte 0xa. Trong trường hợp đó, bạn có thể sử dụng một dấu tách bản ghi khác (với -0ooo).
Stéphane Chazelas

1
@ StéphaneChazelas bạn có thể sử dụng trình tự của chính nó quan tâm như $/, với một (tỉ lệ sử dụng bộ nhớ để khoảng cách tối đa giữa các trình tự như vậy) cân bằng hơi khác nhau:perl -nE 'BEGIN { $/ = "\xef\xbe\xad\xde" } chomp; $c++ unless eof && length; END { say $c }'
Hobbs

@ StéphaneChazelas Vui lòng đọc câu trả lời của tôi để biết giải pháp cho mọi giá trị byte.
sorontar

1
@hobbs, trong mọi trường hợp, ngay cả ở đây, việc sử dụng bộ nhớ sẽ tỷ lệ thuận với khoảng cách tối đa giữa hai byte 0xa mà đối với các tệp không phải là văn bản có thể lớn tùy ý.
Stéphane Chazelas

5

Với GNU awk, bạn có thể làm:

LC_ALL=C awk -v 'RS=\xef\xbe\xad\xde' 'END{print NR - (NR && RT == "")}'

Nếu bất kỳ byte nào là toán tử ERE, chúng sẽ phải được thoát mặc dù (với \\). Giống như 0x2eđó là .sẽ phải được nhập như \\.hay \\\x2e. Ngoài ra, nó nên hoạt động với các giá trị byte tùy ý bao gồm 0 và 0xa.

Lưu ý rằng nó không đơn giản như chỉ NR-1vì có một vài trường hợp đặc biệt:

  • khi đầu vào trống, NR là 0, NR-1 sẽ cho -1.
  • khi đầu vào kết thúc trong dấu tách bản ghi, một bản ghi trống sẽ không được tạo sau đó. Chúng tôi kiểm tra cho điều đó với RT=="".

Cũng lưu ý rằng trong trường hợp xấu nhất (nếu tệp không chứa cụm từ tìm kiếm), tệp sẽ bị tải toàn bộ trong bộ nhớ).


5

Bản dịch đơn giản nhất mà tôi thấy là:

$ echo $'\xef\xbe\xad\xde' > hugohex
$ echo $'\xef\xbe\xad\xde\xef\xbe\xad\xde' >> hugohex
$ grep -F -a -o -e $'\xef\xbe\xad\xde' hugohex|wc -l
3

Nơi mà tôi đã sử dụng $'\xef'như bash ANSI-trích dẫn (ban đầu là một ksh93tính năng, bây giờ được hỗ trợ bởi zsh, bash, mksh, FreeBSD sh) phiên bản của cá nhân \Xef, và sử dụng grep -o ... | wc -lđể đếm các trường hợp. grep -oxuất ra mỗi trận đấu trên một dòng riêng biệt. Các -acờ khiến cư xử grep trên các tập tin nhị phân theo cùng một cách nó trên các tập tin văn bản. -Fdành cho các chuỗi cố định, do đó bạn không cần phải thoát các toán tử regex.

Giống như trong fishtrường hợp của bạn , bạn không thể sử dụng cách tiếp cận đó mặc dù nếu chuỗi cần tìm bao gồm các byte 0 hoặc 0xa (dòng mới trong ASCII).


Sử dụng printf '%b' $(printf '\\%o ' $((0xef)) $((0xbe)) $((0xad)) $((0xde))) > hugohex'sẽ là phương pháp "vỏ tinh khiết" di động nhất. Tất nhiên: printf "efbeadde" | xxd -p -r > hugohexcó vẻ như là phương pháp thiết thực nhất.
sorontar

4

Bạn có thể sử dụng bytes.countphương pháp của Python để có được tổng số các chuỗi con không chồng lấp trong một lần kiểm tra.

python -c "print(open('./myexecutable', 'rb').read().count(b'\xef\xbe\xad\xde'))"

Lớp lót này sẽ tải toàn bộ tệp vào bộ nhớ, do đó không phải là hiệu quả nhất, nhưng hoạt động và dễ đọc hơn Perl; D


'dễ đọc hơn Perl' chỉ là một bước tiến lên từ TECO - mà IINM là: 239I$ 190I$ 173I$ 222I$ HXA ERfile$Y 0UC <:S^EQA$; %C$> QC=(gd & r)
dave_thedom_085

Bạn có thể mmap()một tệp trong Python ; điều đó sẽ làm giảm bộ nhớ cam kết.
Toby Speight

1
tr "$(printf \\0xef)\n" \\n\\0 < infile |
grep -c "^$(printf "\0xbe\0xad\0xde")"

1

Tôi nghĩ bạn có thể sử dụng Perl, hãy dùng thử:

perl -0777ne 'CORE::say STDOUT s/\xef\xbe\xad\xde//g' file_name  

Lệnh thay thế scho số lần thay thế được thực hiện, -0777 có nghĩa là không coi dòng mới là ký tự đặc biệt, e- lệnh thực thi, sayđể in những gì tiếp theo sau đó in ký tự dòng mới, ntôi đã không nắm bắt hoàn toàn, nhưng không hoạt động - từ tài liệu:

khiến Perl giả định vòng lặp sau xung quanh chương trình của bạn, điều này khiến nó lặp đi lặp lại qua các đối số tên tệp có phần giống như sed -n hoặc awk: LINE: while (<>) {... # chương trình của bạn ở đây}

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.