Bagaimana saya bisa menghitung berapa kali urutan byte terjadi dalam file?

16

Saya ingin menghitung berapa kali urutan byte tertentu terjadi di dalam file yang saya miliki. Sebagai contoh, saya ingin mencari tahu berapa kali angka itu \0xdeadbeefterjadi di dalam file yang dapat dieksekusi. Saat ini saya sedang melakukan itu menggunakan grep:

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

(Bytes ditulis dalam urutan terbalik karena CPU saya adalah little-endian)

Namun, saya memiliki dua masalah dengan pendekatan saya:

  • Mereka \Xnnescape sequence hanya bekerja di shell ikan.
  • grep sebenarnya menghitung jumlah garis yang berisi angka ajaibku. Jika polanya muncul dua kali dalam garis yang sama, itu hanya akan dihitung satu kali.

Apakah ada cara untuk memperbaiki masalah ini? Bagaimana saya bisa membuat liner yang satu ini berjalan di Bash shell dan secara akurat menghitung berapa kali pola terjadi di dalam file?

hugomg
sumber
bantuan: unix.stackexchange.com/q/231213/117549 - khusus,grep -o
Jeff Schaller
1
grep adalah alat yang salah untuk digunakan. Pertimbangkan bgrep atau bgrep2.
fpmurphy
3
Jika urutan yang dicari adalah 11221122, seperti apa input yang harus dikembalikan 112211221122? 1 atau 2?
Stéphane Chazelas
Saya akan setuju dengan melaporkan 2 atau 3 pertandingan dalam kasus itu. Mana yang lebih mudah diimplementasikan.
hugomg

Jawaban:

15

Ini adalah solusi satu baris yang diminta (untuk shell baru-baru ini yang memiliki "substitusi proses"):

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

Jika tidak ada "substitusi proses" <(…), gunakan saja grep sebagai filter:

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

Di bawah ini adalah deskripsi terperinci dari setiap bagian dari solusi.

Nilai byte dari angka hex:

Masalah pertama Anda mudah diselesaikan:

Urutan melarikan diri itu hanya bekerja di kulit ikan.

Ubah bagian atas Xke bawah xdan gunakan printf (untuk sebagian besar shell):

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

Atau gunakan:

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

Untuk shell yang memilih untuk tidak mengimplementasikan representasi '\ x'.

Tentu saja, menerjemahkan hex ke octal akan bekerja pada (hampir) shell apa pun:

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

Di mana "$ sh" adalah shell (wajar). Tetapi cukup sulit untuk tetap mengutipnya dengan benar.

File biner.

Solusi yang paling kuat adalah mengubah file dan urutan byte (keduanya) menjadi beberapa pengkodean yang tidak memiliki masalah dengan nilai karakter aneh seperti (baris baru) 0x0Aatau (byte nol) 0x00. Keduanya cukup sulit untuk dikelola dengan benar dengan alat yang dirancang dan diadaptasi untuk memproses "file teks".

Transformasi seperti base64 mungkin tampak valid, tetapi menyajikan masalah bahwa setiap byte input mungkin memiliki hingga tiga representasi output tergantung apakah itu byte pertama, kedua atau ketiga dari posisi mod 24 (bit).

$ echo "abc" | base64
YWJjCg==

$ echo "-abc" | base64
LWFiYwo=

$ echo "--abc" | base64
LS1hYmMK

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

Hex mentransformasi.

Thats why transformasi paling kuat harus menjadi yang dimulai pada setiap batas byte, seperti representasi HEX sederhana.
Kita bisa mendapatkan file dengan representasi hex file dengan salah satu dari alat ini:

$ 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

Urutan byte untuk pencarian sudah dalam hex dalam hal ini.
:

$ var="ef be ad de"

Tetapi bisa juga diubah. Contoh round trip hex-bin-hex berikut:

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

String pencarian dapat diatur dari representasi biner. Salah satu dari tiga opsi yang disajikan di atas od, hexdump, atau xxd adalah setara. Pastikan untuk memasukkan spasi untuk memastikan kecocokan berada pada batas byte (tidak boleh menggeser shift):

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

Jika file biner terlihat seperti ini:

$ 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

Kemudian, pencarian grep sederhana akan memberikan daftar urutan yang cocok:

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

Satu baris?

Itu semua dapat dilakukan dalam satu baris:

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

Misalnya, mencari 11221122dalam file yang sama akan membutuhkan dua langkah ini:

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

Untuk "melihat" kecocokan:

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

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

… 0a 3131323231313232313132323131323231313232313132323131323231313232 313132320a


Buffering

Ada kekhawatiran bahwa grep akan buffer seluruh file, dan, jika file besar, membuat beban berat untuk komputer. Untuk itu, kami dapat menggunakan solusi sed yang tidak disatukan:

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 pertama adalah unbuffered ( -u) dan hanya digunakan untuk menyuntikkan dua baris baru pada aliran per string yang cocok. Keduased hanya akan mencetak garis yang cocok (pendek). Wc -l akan menghitung garis yang cocok.

Ini hanya akan menyangga beberapa garis pendek. String yang cocok di sed kedua. Ini harus cukup rendah dalam sumber daya yang digunakan.

Atau, agak lebih kompleks untuk dipahami, tetapi ide yang sama dalam satu sed:

a='ef be ad de'
hexdump -v -e '/1 "%02x "' infile.bin  |
    sed -u '/\n/P;//!s/'"$a"'/\n&\n/;D' |
        wc -l
sorontar
sumber
2
Perhatikan bahwa jika Anda meletakkan semua teks pada satu baris, itu berarti grepakan berakhir memuat seluruh dalam memori (di sini dua kali ukuran file asli + 1 karena pengkodean hex), jadi pada akhirnya, itu menjadi lebih overhead daripada pythonpendekatan atau yang perldengan -0777. Anda juga memerlukan grepimplementasi yang mendukung garis panjang sewenang-wenang (yang -obiasanya mendukung ). Jawaban yang bagus sebaliknya.
Stéphane Chazelas
1
Versi hex Anda cocok dengan nibble-shifted values? E fb ea dd e? selain byte yang diinginkan. od -An -tx1 | tr -d '\n'atau hexdump -v -e '/1 " %02x"'dengan string pencarian juga mengandung spasi hindari ini, tapi saya tidak melihat perbaikan untuk itu xxd.
dave_thompson_085
@ dave_thompson_085 Jawaban telah diedit. Saya percaya bahwa jawabannya hanya akan cocok dengan batas byte sekarang, Terima kasih lagi.
sorontar
@ StéphaneChazelas Bisakah Anda meninjau opsi yang diusulkan untuk menggunakan sed yang tidak terbaca. Terima kasih.
sorontar
sed -u(jika tersedia) untuk unbuffer. Itu berarti akan membaca satu byte pada satu waktu pada input, dan output outputnya langsung tanpa buffering. Di anycase, masih perlu memuat seluruh garis di ruang pola, jadi tidak akan membantu di sini.
Stéphane Chazelas
7

Dengan GNU grep's -P(perl-regexp) flag

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

LC_ALL=C adalah untuk menghindari masalah di tempat multi-byte di mana grep kalau tidak akan mencoba untuk menafsirkan urutan byte sebagai karakter.

-amemperlakukan file biner yang setara dengan file teks (alih-alih perilaku normal, di mana grephanya mencetak apakah setidaknya ada satu yang cocok atau tidak)

iruvar
sumber
Solusi ini selalu memberi saya 0 kecocokan bukannya angka yang benar.
hugomg
@hugomg, mungkinkah Anda perlu membalik byte yang diteruskan grep agar cocok?
iruvar
Saya tidak berpikir itu adalah urutannya. Dua jawaban lain untuk pertanyaan ini bekerja dengan benar.
hugomg
2
@Hugomg, ini adalah lokal. Lihat edit.
Stéphane Chazelas
2
Saya akan menyarankan untuk menyertakan -aopsi, jika tidak grep akan menjawab dengan Binary file file.bin matchesuntuk file apa pun yang grep mendeteksi sebagai biner.
sorontar
6
PERLIO=:raw perl -nE '$c++ while m/\xef\xbe\xad\xde/g; END{say $c}' file

Yang memperlakukan file input sebagai biner (tidak ada terjemahan untuk pengumpanan atau penyandian baris, lihat perlrun ) kemudian lilitkan pada file input yang tidak mencetak incrementing counter untuk semua kecocokan dari hex yang diberikan (atau bentuk apa pun, lihat perlre ) .

thrig
sumber
2
Perhatikan bahwa Anda tidak dapat menggunakannya jika urutan untuk mencari berisi byte 0xa. Dalam hal ini, Anda dapat menggunakan pemisah rekaman yang berbeda (dengan -0ooo).
Stéphane Chazelas
1
@ StéphaneChazelas Anda dapat menggunakan urutan minat itu sendiri $/, dengan tradeoff yang sedikit berbeda (penggunaan memori sebanding dengan jarak maksimum antara urutan tersebut):perl -nE 'BEGIN { $/ = "\xef\xbe\xad\xde" } chomp; $c++ unless eof && length; END { say $c }'
hobbs
@ StéphaneChazelas Harap baca jawaban saya untuk solusi untuk setiap nilai byte.
sorontar
1
@ hobbs, dalam hal apa pun, bahkan di sini, penggunaan memori akan sebanding dengan jarak maksimum antara dua 0xa byte yang untuk file non-teks bisa menjadi besar secara sewenang-wenang.
Stéphane Chazelas
5

Dengan GNU awk, Anda dapat melakukan:

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

Jika ada byte yang merupakan operator ERE, mereka harus lolos (dengan \\). Seperti 0x2eyang .harus dimasukkan sebagai \\.atau \\\x2e. Selain itu, itu harus bekerja dengan nilai byte sembarang termasuk 0 dan 0xa.

Perhatikan bahwa ini tidak sesederhana hanya NR-1karena ada beberapa kasus khusus:

  • ketika input kosong, NR adalah 0, NR-1 akan memberikan -1.
  • ketika input berakhir di pemisah rekaman, catatan kosong tidak dibuat setelah itu. Kami menguji untuk itu dengan RT=="".

Perhatikan juga bahwa dalam kasus terburuk (jika file tidak mengandung istilah pencarian), file tersebut akan berakhir dimuat seluruhnya dalam memori).

Stéphane Chazelas
sumber
5

Terjemahan paling mudah yang saya lihat adalah:

$ 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

Di mana saya telah menggunakan $'\xef'sebagai bash ANSI-quoting (awalnya aksh93 fitur, sekarang didukung oleh zsh, bash, mksh, FreeBSD sh) versi ikan \Xef, dan digunakan grep -o ... | wc -luntuk menghitung contoh. grep -ooutput setiap pertandingan pada baris yang terpisah. The -aflag membuat berperilaku grep pada file biner dengan cara yang sama dilakukannya pada file teks. -Fadalah untuk string tetap sehingga Anda tidak perlu melarikan diri dari operator regex.

Seperti pada fishkasus Anda, Anda tidak dapat menggunakan pendekatan itu jika urutan yang dicari termasuk byte 0 atau 0xa (baris baru di ASCII).

Jeff Schaller
sumber
Menggunakan printf '%b' $(printf '\\%o ' $((0xef)) $((0xbe)) $((0xad)) $((0xde))) > hugohex'akan menjadi metode "cangkang murni" yang paling portabel. Tentu saja: printf "efbeadde" | xxd -p -r > hugohexsepertinya metode yang paling praktis.
sorontar
4

Anda bisa menggunakan Python bytes.count metode untuk mendapatkan jumlah total substring yang tidak tumpang tindih dalam bytestring.

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

One-liner ini akan memuat seluruh file ke dalam memori, jadi bukan yang paling efisien, tetapi bekerja dan lebih terbaca daripada Perl; D

Nick T
sumber
'lebih terbaca daripada Perl' hanya satu langkah dari TECO - yang IINM adalah: 239I$ 190I$ 173I$ 222I$ HXA ERfile$Y 0UC <:S^EQA$; %C$> QC=(gd & r)
dave_thompson_085
Anda dapat mmap()file dalam Python ; yang akan mengurangi komit memori.
Toby Speight
1
tr "$(printf \\0xef)\n" \\n\\0 < infile |
grep -c "^$(printf "\0xbe\0xad\0xde")"
mikeserv
sumber
1

Saya pikir Anda dapat menggunakan Perl, cobalah:

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

Ganti perintah smemberikan jumlah penggantian yang dibuat, -0777 berarti tidak memperlakukan baris baru sebagai karakter khusus, e- mengeksekusi perintah, sayuntuk mencetak apa yang terjadi kemudian mencetak karakter baris baru,n saya belum sepenuhnya dipahami, tetapi tidak berhasil w / out - dari dokumen:

menyebabkan Perl untuk menganggap loop berikut di sekitar program Anda, yang membuatnya lebih dari argumen nama file agak seperti sed -n atau awk: LINE: while (<>) {... # program Anda ada di sini}

Alexei Martianov
sumber