Bir bayt dizisinin bir dosyada kaç kez meydana geldiğini nasıl sayabilirim?


16

Sahip olduğum bir dosya içinde belirli bir bayt dizisinin kaç kez olduğunu saymak istiyorum. Örneğin, sayının \0xdeadbeefyürütülebilir bir dosyada kaç kez oluştuğunu öğrenmek istiyorum . Şu anda bunu grep kullanarak yapıyorum:

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

(CPU'm küçük endian olduğu için baytlar ters sırada yazılır)

Ancak yaklaşımımla ilgili iki problemim var:

  • Bu \Xnnkaçış dizileri sadece balık kabuğunda çalışır.
  • grep aslında benim sihirli numaramı içeren satır sayısını sayıyor. Desen aynı satırda iki kez oluşursa, yalnızca bir kez sayılır.

Bu sorunları çözmenin bir yolu var mı? Bu tek astarın Bash kabuğunda çalışmasını ve desenin dosya içinde kaç kez gerçekleştiğini doğru bir şekilde sayabilirim?


biraz yardım: unix.stackexchange.com/q/231213/117549 - özellikle,grep -o
Jeff Schaller

1
grep, yanlış bir araçtır. Bgrep veya bgrep2'yi düşünün.
fpmurphy

3
Aranacak sıra ise 11221122, aşağıdaki gibi bir girdide ne döndürülmelidir 112211221122? 1 yada 2?
Stéphane Chazelas

Bu durumda 2 veya 3 maç bildirerek sorun olmaz. Hangisinin uygulanması daha kolay olurdu.
hugomg

Yanıtlar:


15

Bu istenen tek astarlı çözümdür ("proses ikamesi" olan son mermiler için):

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

"İşlem ikamesi" <(…)yoksa, filtre olarak grep kullanın:

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

Aşağıda, çözümün her bir bölümünün ayrıntılı açıklaması verilmiştir.

Onaltılık sayılardan gelen bayt değerleri:

İlk sorununuzu çözmek kolaydır:

Bu \ Xnn kaçış dizileri sadece balık kabuğunda çalışır.

Üst kısmı Xaşağıya doğru değiştirin xve printf kullanın (çoğu kabuk için):

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

Ya da kullan:

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

'\ X' temsilini uygulamamayı seçen mermiler için.

Tabii ki, hex'i octal'a çevirmek herhangi bir kabuk üzerinde (neredeyse) çalışacaktır:

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

"$ Sh" herhangi (makul) bir kabuk olduğunda. Ancak doğru bir şekilde alıntı yapmak oldukça zordur.

İkili dosyalar.

En sağlam çözüm, dosyayı ve bayt dizisini (her ikisini), (yeni satır) 0x0Aveya (boş bayt) gibi tek karakter değerleriyle hiçbir sorunu olmayan bazı kodlamalara dönüştürmektir 0x00. Her ikisinin de "metin dosyalarını" işlemek için tasarlanmış ve uyarlanmış araçlarla doğru şekilde yönetilmesi oldukça zordur.

Base64 gibi bir dönüşüm geçerli gibi görünebilir, ancak mod 24 (bit) konumunun birinci, ikinci veya üçüncü bayt olmasına bağlı olarak her giriş baytının üç çıkış gösterimine sahip olabileceği sorununu sunar.

$ echo "abc" | base64
YWJjCg==

$ echo "-abc" | base64
LWFiYwo=

$ echo "--abc" | base64
LS1hYmMK

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

Hex dönüşümü.

Bu nedenle en sağlam dönüşüm, basit HEX temsili gibi her bayt sınırında başlayan bir dönüşüm olmalıdır.
Bu araçlardan herhangi biriyle dosyanın onaltılı temsilini içeren bir dosya alabiliriz:

$ 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

Bu durumda aranacak bayt dizisi zaten onaltılıdır.
:

$ var="ef be ad de"

Ama aynı zamanda dönüştürülebilir. Bir gidiş-dönüş hex-bin-hex örneği aşağıdadır:

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

Arama dizgisi ikili gösterimden ayarlanabilir. Yukarıda sunulan üç seçenekten herhangi biri od, hexdump veya xxd eşdeğerdir. Maçın bayt sınırlarında olduğundan emin olmak için boşluk eklediğinizden emin olun (kemirmek kaydırmaya izin verilmez):

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

İkili dosya şöyle görünürse:

$ 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

Ardından, basit bir grep araması eşleşen dizilerin listesini verecektir:

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

Tek çizgi?

Her şey bir satırda yapılabilir:

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

Örneğin 11221122, aynı dosyada arama yapmak için şu iki adım gerekir:

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

Eşleşmeleri "görmek" için:

$ 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


tamponlama

Grep'in tüm dosyayı arabelleğe alacağına dair bir endişe var ve dosya büyükse, bilgisayar için ağır bir yük oluşturuyor. Bunun için tamponlanmamış bir sed çözümü kullanabiliriz:

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

İlk sed arabelleğe alınmış ( -u) değildir ve yalnızca eşleşen dize başına akışa iki yeni satır enjekte etmek için kullanılır. İkincisi sedsadece (kısa) eşleşen çizgileri basacaktır. Wc -l eşleşen satırları sayar.

Bu sadece bazı kısa satırları tamponlayacaktır. İkinci sed'deki eşleşen dize (ler). Kullanılan kaynaklarda bu oldukça düşük olmalıdır.

Ya da, anlaşılması biraz daha karmaşık, ama aynı fikir bir sed'de:

a='ef be ad de'
hexdump -v -e '/1 "%02x "' infile.bin  |
    sed -u '/\n/P;//!s/'"$a"'/\n&\n/;D' |
        wc -l

2
Tüm metni bir satıra koyarsanız, bunun anlamı grepbelleğe tamamen yükleneceği anlamına gelir (burada onaltılık kodlama nedeniyle orijinal dosyanın boyutunun iki katı + 1), böylece sonunda daha fazla olur havai daha pythonyaklaşımla ya perlsahip biri -0777. Ayrıca grep, keyfi uzunluktaki satırları ( -ogenellikle destekleyenler) destekleyen bir uygulamaya da ihtiyacınız vardır . Aksi takdirde iyi cevap.
Stéphane Chazelas

1
Onaltılık sürümleriniz, nibble kaydırmalı değerlerle eşleşiyor mu? E fb ea dd e? istenen baytlara ek olarak. od -An -tx1 | tr -d '\n'veya hexdump -v -e '/1 " %02x"'boşluk içeren bir arama dizesi ile bu önlemek, ama böyle bir düzeltme görmüyorum xxd.
dave_thompson_085

@ dave_thompson_085 Yanıt düzenlendi. Cevabın şu anda sadece bayt sınırlarıyla eşleşeceğine inanıyorum, Tekrar teşekkürler.
sorontar

@ StéphaneChazelas Tamponlu olmayan bir sed kullanmanın önerilen seçeneğini inceleyebilir misiniz? Teşekkürler.
sorontar

sed -u(varsa) arabelleğe alma içindir. Bu, girişte bir kerede bir bayt okuyacağı ve çıktısını arabelleğe almadan hemen çıkaracağı anlamına gelir. Her halükarda, tüm çizgiyi desen alanına yüklemesi gerekecektir, bu yüzden burada yardımcı olmaz.
Stéphane Chazelas

7

GNU'da grep'in -P(Perl regexp) bayrak

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

LC_ALL=Cbayt grepdizilerini karakter olarak yorumlamaya çalışacağı çok baytlı yerel ortamlardaki sorunlardan kaçınmaktır .

-ametin dosyalarına eşdeğer ikili dosyaları ele alır ( grepyalnızca en az bir eşleşme olup olmadığını yazdırdığı normal davranış yerine )


Bu çözüm her zaman doğru sayı yerine 0 eşleşme veriyor.
hugomg

@hugomg, grep eşleşmesi için geçen baytları tersine çevirmeniz gerekebilir mi?
iruvar

Bunun emir olduğunu sanmıyorum. Bu sorunun diğer iki yanıtı doğru çalışıyor.
hugomg

2
@ hugomg, bu yerel ayar. Bkz. Düzenleme.
Stéphane Chazelas

2
-aSeçeneği eklemenizi öneririm , aksi takdirde grep, Binary file file.bin matchesgrep'in ikili olarak algıladığı herhangi bir dosya için cevap verecektir .
sorontar

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

Girdi dosya (lar) ını ikili olarak ele alır (satır beslemeleri veya kodlamalar için çeviri yoktur, perlrun'a bakın ), daha sonra verilen onaltılı tüm eşleşmeler (veya herhangi bir form, perlre'ye bakın ) için bir sayacı arttırmayan giriş dosya (lar) ına döner. .


2
Aranacak sekans bayt 0xa içeriyorsa bunu kullanamayacağınızı unutmayın. Bu durumda, farklı bir kayıt ayırıcı (ile -0ooo) kullanabilirsiniz.
Stéphane Chazelas

1
@ StéphaneChazelas, ilgi dizisini, $/biraz farklı bir takas (bellek kullanımı gibi bu diziler arasındaki maksimum mesafeyle orantılı) olarak kullanabilirsiniz:perl -nE 'BEGIN { $/ = "\xef\xbe\xad\xde" } chomp; $c++ unless eof && length; END { say $c }'
ocaklar

@ StéphaneChazelas Herhangi bir bayt değeri için bir çözüm bulmak için lütfen cevabımı okuyun.
sorontar

1
@hobbs, her durumda, burada bile, bellek kullanımı, metin olmayan dosyalar için keyfi olarak büyük olabilecek iki 0xa bayt arasındaki maksimum mesafe ile orantılı olacaktır.
Stéphane Chazelas

5

GNU ile awkşunları yapabilirsiniz:

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

Baytlardan herhangi biri ERE operatörüyse, (ile \\) olsa da kaçmaları gerekir . Gibi 0x2ehangi .şekilde girilmesi gerekir \\.ya \\\x2e. Bunun dışında 0 ve 0xa da dahil olmak üzere rastgele bayt değerleri ile çalışmalıdır.

Bunun sadece NR-1birkaç özel durum olduğu için basit olmadığını unutmayın :

  • giriş boş olduğunda NR 0'dır, NR-1 -1 verir.
  • giriş kayıt ayırıcıda sona erdiğinde bundan sonra boş bir kayıt oluşturulmaz. Bunu test ediyoruz RT=="".

Ayrıca en kötü durumda (dosya arama terimini içermiyorsa) dosyanın belleğe tamamen yükleneceğini unutmayın.


5

Gördüğüm en basit çeviri:

$ 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

Ben kullandım nerede $'\xef'olarak ANSI-alıntı Bash (aslında bir ksh93özellik, şimdi tarafından desteklenen zsh, bash, mksh, FreeBSD sh) balık en versiyonu \Xefve kullanılan grep -o ... | wc -lörneklerini saymak. grep -oher eşleşmeyi ayrı bir satıra çıkarır. -aBayrak ikili dosyalar üzerinde o metin dosyaları üzerinde yaptığı biçimde grep davranmak yapar. -Fsabit dizeler içindir, bu nedenle normal ifade operatörlerinden kaçmanıza gerek yoktur.

Sizin fishdurumunuzda olduğu gibi, aranacak sıra 0 veya 0xa baytlarını içeriyorsa (ASCII'de yeni satır) bu yaklaşımı kullanamazsınız.


Kullanmak printf '%b' $(printf '\\%o ' $((0xef)) $((0xbe)) $((0xad)) $((0xde))) > hugohex'en taşınabilir "saf kabuk" yöntemi olacaktır. Tabii ki: printf "efbeadde" | xxd -p -r > hugohexen pratik yöntem gibi görünüyor.
sorontar

4

bytes.countBir baytlamadaki çakışmayan alt dizelerin toplam sayısını elde etmek için Python'un yöntemini kullanabilirsiniz .

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

Bu tek astar, tüm dosyayı belleğe yükleyecektir, bu yüzden en verimli değildir, ancak çalışır ve Perl'den daha okunaklıdır; D


'Perl'den daha okunaklı' TECO'dan sadece bir adım yukarı - ki bu IINM: 239I$ 190I$ 173I$ 222I$ HXA ERfile$Y 0UC <:S^EQA$; %C$> QC=(gd & r)
dave_thompson_085

mmap()Python'da bir dosya yapabilirsiniz ; bu da bellek taahhüdünü azaltacaktır.
Toby Speight

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

1

Sanırım Perl'i kullanabilirsiniz, bir deneyin:

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

Değiştir komutu s, yapılan değişiklik sayısını verir, -0777 yeni satırı özel karakter olarak ele almaz, e- komutu çalıştır,say sonraki adımda yazdırdıktan sonra yeni satır karakterini yazdırır, ntamamen kavramadım, ancak w / out - çalışmıyor docs:

Perl'in programınızın etrafında aşağıdaki döngüyü almasına neden olur, bu da sed -n veya awk gibi dosya adı argümanları üzerinde yinelenmesini sağlar: LINE: while (<>) {... # programınız buraya gider}

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.