ฉันจะนับจำนวนครั้งที่ลำดับไบต์เกิดขึ้นในไฟล์ได้อย่างไร


16

ฉันต้องการนับกี่ครั้งที่ลำดับของไบต์เกิดขึ้นภายในไฟล์ที่ฉันมี ตัวอย่างเช่นฉันต้องการค้นหาจำนวนครั้งที่\0xdeadbeefเกิดขึ้นภายในไฟล์ที่ปฏิบัติการได้ ตอนนี้ฉันกำลังใช้ grep:

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

(ไบต์ถูกเขียนในลำดับย้อนกลับเนื่องจาก CPU ของฉันเป็น endian น้อย)

อย่างไรก็ตามฉันมีปัญหาสองประการเกี่ยวกับวิธีการของฉัน:

  • \Xnnลำดับการหลบหนีเหล่านั้นใช้ได้เฉพาะในเปลือกปลาเท่านั้น
  • grep กำลังนับจำนวนบรรทัดที่มีหมายเลขเวทย์มนตร์ของฉัน หากรูปแบบเกิดขึ้นสองครั้งในบรรทัดเดียวกันมันจะนับเพียงครั้งเดียว

มีวิธีแก้ไขปัญหาเหล่านี้หรือไม่? ฉันจะทำให้หนึ่งซับนี้ทำงานในเปลือก Bash และนับจำนวนครั้งอย่างถูกต้องรูปแบบที่เกิดขึ้นภายในไฟล์ได้อย่างไร


ความช่วยเหลือ: unix.stackexchange.com/q/231213/117549 - โดยเฉพาะgrep -o
Jeff Schaller

1
grep เป็นเครื่องมือที่ผิดที่จะใช้ พิจารณา bgrep หรือ bgrep2
fpmurphy

3
หากลำดับที่จะค้นหาคือ11221122สิ่งที่ควรกลับในการป้อนข้อมูลเช่น112211221122? 1 หรือ 2
Stéphane Chazelas

ฉันจะตกลงกับการรายงาน 2 หรือ 3 การแข่งขันในกรณีนั้น แล้วแต่ว่าการใช้งานแบบใดจะง่ายกว่า
hugomg

คำตอบ:


15

นี่คือโซลูชันแบบหนึ่งซับขอ (สำหรับเชลล์ล่าสุดที่มี "การทดแทนกระบวนการ"):

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 เหล่านั้นใช้ได้เฉพาะในเปลือกปลา

เปลี่ยนด้านบนXเป็นอันต่ำกว่าxและใช้ printf (สำหรับเชลล์ส่วนใหญ่):

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

หรือใช้:

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

สำหรับเชลล์เหล่านั้นที่เลือกที่จะไม่ใช้การแทน '\ x'

แน่นอนการแปลฐานสิบหกเป็นฐานแปดจะทำงานบน (เกือบ) เชลล์ใด ๆ :

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

โดยที่ "$ sh" คือเปลือกใด ๆ (สมเหตุสมผล) แต่มันก็ค่อนข้างยากที่จะรักษาไว้อย่างถูกต้อง

ไฟล์ไบนารี

วิธีการแก้ปัญหาที่มีประสิทธิภาพมากที่สุดคือการแปลงไฟล์และลำดับไบต์ (ทั้ง) เพื่อเข้ารหัสบางส่วนที่มีปัญหาเกี่ยวกับค่าตัวละครที่แปลกไม่เหมือนใคร (สายใหม่) 0x0Aหรือ (null 0x00ไบต์) ทั้งสองค่อนข้างยากที่จะจัดการอย่างถูกต้องด้วยเครื่องมือที่ออกแบบและปรับให้เหมาะกับการประมวลผล "ไฟล์ข้อความ"

การแปลงเช่น base64 อาจดูเหมือนถูกต้อง แต่นำเสนอปัญหาที่ทุกไบต์อินพุตอาจมีการแทนเอาต์พุตสูงสุดถึงสามขึ้นอยู่กับว่ามันเป็นไบต์แรกสองหรือสามของตำแหน่ง mod 24 (บิต)

$ echo "abc" | base64
YWJjCg==

$ echo "-abc" | base64
LWFiYwo=

$ echo "--abc" | base64
LS1hYmMK

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

แปลงเลขฐานสิบหก

นั่นเป็นสาเหตุที่การเปลี่ยนแปลงที่แข็งแกร่งที่สุดควรเริ่มต้นที่ขอบเขตของแต่ละไบต์เช่นการแทนค่า HEX แบบง่าย ๆ
เราสามารถรับไฟล์ด้วยการแสดงเลขฐานสิบหกของไฟล์ด้วยเครื่องมือนี้:

$ 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"

แต่มันสามารถเปลี่ยนได้ ตัวอย่างของ hex-bin-hex ไปกลับดังนี้:

$ 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' ' ')

… 0a 313132323131323231313232313132323131323231313232313132323131323231313232 313132320a


บัฟเฟอร์

มีข้อกังวลว่า grep จะบัฟเฟอร์ไฟล์ทั้งหมดและถ้าไฟล์มีขนาดใหญ่ให้สร้างภาระหนักสำหรับคอมพิวเตอร์ สำหรับสิ่งนั้นเราอาจใช้วิธีการแก้ปัญหาแบบไม่บุบสลาย:

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
โปรดทราบว่าหากคุณใส่ข้อความทั้งหมดในหนึ่งบรรทัดนั่นหมายความว่าgrepจะเป็นการโหลดทั้งหมดลงในหน่วยความจำ (นี่คือขนาดไฟล์ใหญ่เป็นสองเท่าของเดิม + 1 เนื่องจากการเข้ารหัสฐานสิบหก) ดังนั้นในท้ายที่สุดมันจะจบลงด้วยการเพิ่มเติม ค่าใช้จ่ายกว่าpythonวิธีการหรือเป็นหนึ่งเดียวกับperl -0777นอกจากนี้คุณยังจำเป็นต้องมีgrepการดำเนินงานที่สนับสนุนสายความยาวโดยพลการ (ผู้ที่สนับสนุน-oโดยทั่วไปทำ) คำตอบที่ดีเป็นอย่างอื่น
Stéphane Chazelas

1
รุ่นฐานสิบหกของคุณตรงกับค่าที่เลื่อนไปทางไหน e fb ea dd e นอกเหนือจากไบต์ที่ต้องการ od -An -tx1 | tr -d '\n'หรือhexdump -v -e '/1 " %02x"'ด้วยสตริงการค้นหาที่มีช่องว่างหลีกเลี่ยงสิ่งนี้ แต่ฉันไม่เห็นวิธีแก้ไขเช่นxxdนี้
dave_thompson_085

@ dave_thompson_085 คำตอบแก้ไข ฉันเชื่อว่าคำตอบจะตรงกับขอบเขตของไบต์ในขณะนี้ขอบคุณอีกครั้ง
sorontar

@ StéphaneChazelasคุณสามารถตรวจสอบตัวเลือกที่เสนอในการใช้ sed ที่ไม่มีบัฟเฟอร์ ขอบคุณ
sorontar

sed -u(หากมี) สำหรับ unbuffer นั่นหมายความว่ามันจะอ่านทีละหนึ่งไบต์ในอินพุตและเอาต์พุตของมันทันทีโดยไม่บัฟเฟอร์ ในกรณีใด ๆ ก็ยังคงต้องโหลดทั้งบรรทัดในพื้นที่รูปแบบดังนั้นจะไม่ช่วยที่นี่
Stéphane Chazelas

7

ด้วยการตั้งค่าสถานะgrepของGNU -P(perl-regexp)

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

LC_ALL=Cคือการหลีกเลี่ยงปัญหาในโลแคลหลายไบต์ซึ่งgrepอาจพยายามตีความลำดับของไบต์เป็นอักขระ

-aปฏิบัติกับไฟล์ไบนารีเทียบเท่ากับไฟล์ข้อความ (แทนที่จะเป็นพฤติกรรมปกติโดยgrepจะพิมพ์ออกมาว่ามีการจับคู่อย่างน้อยหนึ่งรายการหรือไม่)


วิธีนี้ให้ฉันเสมอ 0 แมทช์แทนที่จะเป็นเบอร์ที่ถูกต้อง
hugomg

@hugomg เป็นไปได้ไหมว่าคุณต้องย้อนกลับจำนวนไบต์ที่ส่งผ่านเพื่อgrep ให้ตรงกับหรือไม่
iruvar

ฉันไม่คิดว่ามันเป็นคำสั่ง อีกสองคำตอบสำหรับคำถามนี้ทำงานอย่างถูกต้อง
hugomg

2
@hugomg มันเป็นสถานที่เกิดเหตุ ดูการแก้ไข
Stéphane Chazelas

2
ฉันจะแนะนำให้รวม-aตัวเลือกมิฉะนั้น grep จะตอบด้วยBinary file file.bin matchesสำหรับไฟล์ใด ๆ ที่ grep ตรวจพบว่าเป็นไบนารี
sorontar

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

ซึ่งถือว่าไฟล์อินพุตเป็นไบนารี (ไม่มีการแปลสำหรับ linefeeds หรือการเข้ารหัสดูperlrun ) จากนั้นวนรอบไฟล์อินพุตที่ไม่พิมพ์เคาน์เตอร์ที่เพิ่มขึ้นสำหรับการแข่งขันทั้งหมดของ hex ที่กำหนด (หรือรูปแบบใด ๆ ดูperlre ) .


2
โปรดทราบว่าคุณไม่สามารถใช้สิ่งนั้นได้หากลำดับการค้นหามีไบต์ 0xa ในกรณีนั้นคุณสามารถใช้ตัวแยกเร็กคอร์ดอื่น (พร้อม-0ooo)
Stéphane Chazelas

1
@ StéphaneChazelasคุณสามารถใช้ลำดับของดอกเบี้ยเอง$/ด้วยการแลกเปลี่ยนที่แตกต่างกันเล็กน้อย (การใช้หน่วยความจำตามสัดส่วนระยะทางสูงสุดระหว่างลำดับดังกล่าว):perl -nE 'BEGIN { $/ = "\xef\xbe\xad\xde" } chomp; $c++ unless eof && length; END { say $c }'
hobbs

@ StéphaneChazelasโปรดอ่านคำตอบของฉันสำหรับวิธีแก้ปัญหาสำหรับค่าไบต์ใด ๆ
sorontar

1
@ ฮอบส์ไม่ว่าในกรณีใดก็ตามถึงแม้ที่นี่การใช้หน่วยความจำจะเป็นสัดส่วนกับระยะทางสูงสุดระหว่างสอง 0xa ไบต์ซึ่งสำหรับไฟล์ที่ไม่ใช่ข้อความอาจมีขนาดใหญ่โดยพลการ
Stéphane Chazelas

5

ด้วย GNU awkคุณสามารถทำได้:

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

หากไบต์ใด ๆ เป็นโอเปอเรเตอร์ ERE จะต้องมีการหลบหนีแม้ว่า (พร้อม\\) ชอบ0x2eซึ่งเป็น.จะต้องได้รับการป้อนเป็นหรือ\\. \\\x2eนอกจากนั้นควรทำงานกับค่าไบต์ที่กำหนดเองรวมถึง 0 และ 0xa

โปรดทราบว่ามันไม่ง่ายเหมือนเพียงNR-1เพราะมีสองกรณีพิเศษ:

  • เมื่ออินพุตว่าง NR คือ 0, NR-1 จะให้ -1
  • เมื่ออินพุตสิ้นสุดในตัวคั่นเร็กคอร์ดเร็กคอร์ดว่างจะไม่ถูกสร้างหลังจากนั้น RT==""เราจะทดสอบสำหรับการที่มี

นอกจากนี้โปรดทราบว่าในกรณีที่เลวร้ายที่สุด (หากไฟล์ไม่มีข้อความค้นหา) ไฟล์นั้นจะถูกโหลดลงในหน่วยความจำทั้งหมด)


5

คำแปลที่ตรงไปตรงมาที่สุดที่ฉันเห็นคือ:

$ 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

ที่ผมเคยใช้$'\xef'เป็นทุบตี ANSI-quoting (เดิมเป็นksh93คุณลักษณะที่ได้รับการสนับสนุนในขณะนี้โดยzsh, bash, mksh, FreeBSD sh) ของปลา\Xefและใช้grep -o ... | wc -lในการนับกรณี grep -oเอาต์พุตแต่ละคู่ที่ตรงกันบนบรรทัดแยกต่างหาก การ-aตั้งค่าสถานะทำให้ grep ทำงานกับไฟล์ไบนารีเช่นเดียวกับที่ทำกับไฟล์ข้อความ -Fมีไว้สำหรับสตริงคงที่ดังนั้นคุณไม่จำเป็นต้องหลบเลี่ยงโอเปอเรเตอร์ regex

เช่นเดียวกับในfishกรณีของคุณคุณไม่สามารถใช้วิธีการนั้นแม้ว่าลำดับการค้นหาจะรวมไบต์ 0 หรือ 0xa (บรรทัดใหม่ใน ASCII)


การใช้printf '%b' $(printf '\\%o ' $((0xef)) $((0xbe)) $((0xad)) $((0xde))) > hugohex'จะเป็นวิธี "เปลือกบริสุทธิ์" แบบพกพามากที่สุด แน่นอน: printf "efbeadde" | xxd -p -r > hugohexดูเหมือนว่าวิธีการปฏิบัติมากที่สุด
sorontar

4

คุณสามารถใช้bytes.countวิธีของ Python เพื่อรับจำนวน substrings ที่ไม่ทับซ้อนกันในการทดสอบ

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

หนึ่งซับนี้จะโหลดไฟล์ทั้งหมดลงในหน่วยความจำจึงไม่ได้มีประสิทธิภาพมากที่สุด แต่ใช้งานได้และอ่านง่ายกว่า Perl; D


'ชัดเจนกว่า Perl' เป็นเพียงขั้นตอนเดียวจาก TECO - ซึ่ง IINM คือ: 239I$ 190I$ 173I$ 222I$ HXA ERfile$Y 0UC <:S^EQA$; %C$> QC=(gd & r)
dave_thompson_085

คุณสามารถmmap()ไฟล์ใน Python ; ที่จะลดหน่วยความจำที่กระทำ
Toby Speight


1

ฉันคิดว่าคุณสามารถใช้ Perl ลองได้:

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

แทนที่คำสั่งsให้จำนวนของการแทนที่ที่ทำ -0777 หมายถึงไม่ถือว่าบรรทัดใหม่เป็นอักขระพิเศษe- รันคำสั่งsayเพื่อพิมพ์สิ่งที่จะไปต่อไปแล้วพิมพ์อักขระบรรทัดใหม่nฉันไม่ได้เข้าใจอย่างเต็มที่ แต่ไม่ได้ทำงาน w / ออก - จาก เอกสาร:

ทำให้ Perl ถือว่าวงรอบต่อไปนี้โปรแกรมของคุณซึ่งทำให้มันย้ำกว่าข้อโต้แย้งชื่อไฟล์ค่อนข้างเช่น sed -n หรือ awk: LINE: ในขณะที่ (<>) {... # โปรแกรมของคุณไปที่นี่}

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.