Як я можу підрахувати кількість разів у файлі послідовності байтів?


16

Я хочу порахувати, скільки разів певна послідовність байтів трапляється всередині файлу, який у мене є. Наприклад, я хочу дізнатися, скільки разів число \0xdeadbeefвиникає всередині виконуваного файлу. Зараз я роблю це за допомогою grep:

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

(Байти записуються у зворотному порядку, тому що мій процесор є малоінтенсивним)

Однак у мене з підходом дві проблеми:

  • Ці \Xnnпослідовності втечі працюють лише в шкаралупі риби.
  • grep насправді підраховує кількість рядків, які містять моє магічне число. Якщо візерунок зустрічається двічі в одному рядку, він буде рахуватися лише один раз.

Чи є спосіб виправити ці проблеми? Як я можу змусити цей вкладиш запуститись у оболонці Bash і точно підрахувати кількість разів, коли візерунок трапляється у файлі?


допомога: unix.stackexchange.com/q/231213/117549 - конкретно,grep -o
Jeff Schaller

1
grep - неправильний інструмент для використання. Розглянемо bgrep або bgrep2.
fpmurphy

3
Якщо послідовність пошуку є 11221122, що потрібно повернути на вхід 112211221122? 1 або 2?
Стефан Шазелас

Я був би в порядку, повідомляючи про 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або (нульовий байт) 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"

Але це також може бути перетворене. Приклад зворотної поїздки шестигранника:

$ 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 -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 3131323231313232313132323131323231313232313132323131323231313232 313132320a


Буферизація

Існує занепокоєння, що 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
Зауважте, що якщо ви помістите весь текст в один рядок, це означає, що grepв кінцевому підсумку ви завантажите його цілком у пам'ять (тут вдвічі більший розмір вихідного файлу + 1 через шестнадцяткову кодування), тож, врешті-решт, він виявиться більше накладні, ніж pythonпідхід чи той, що perlмає -0777. Вам також потрібна grepреалізація, яка підтримує лінії довільної довжини (ті, які -oзазвичай підтримують ). Гарна відповідь інакше.
Стефан Шазелас

1
Ваші шістнадцяткові версії відповідають значенням, зміщеним за допомогою викручування? E fb ea dd e? крім бажаних байтів. od -An -tx1 | tr -d '\n'або hexdump -v -e '/1 " %02x"'з рядком пошуку, що також містить пробіли, уникайте цього, але я не бачу такого виправлення xxd.
dave_thompson_085

@ dave_thompson_085 Відповідь відредаговано. Я вірю, що відповідь відповідатиме лише за межами байтів, ще раз спасибі.
соронтар

@ StéphaneChazelas Чи можете ви переглянути запропонований варіант використання нерозподіленого sed. Спасибі.
соронтар

sed -u(де є) призначено для скасування. Це означає, що він буде читати один байт по черзі на вході, і виводить його вихід відразу без буферизації. У будь-якому випадку все одно потрібно буде завантажити весь рядок у простір шаблону, тому тут не допоможе.
Стефан Шазелас

7

У GNU grep«s -P(Perl-регулярних виразів) прапор

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, це місце. Див. Редагування.
Стефан Шазелас

2
Я пропоную включити -aопцію, інакше grep відповість Binary file file.bin matchesза будь-який файл, який grep визначає як двійковий.
соронтар

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

Який трактує вхідні файли (файли) як двійкові (немає перекладу для каналів ліній або кодування, див. Perlrun ), а потім петлі над вхідними файлами (файлами), не друкуючи приріст лічильника для всіх збігів даного шестигранника (або будь-якої форми, див. Perlre ) .


2
Зауважте, що ви не можете використовувати це, якщо послідовність пошуку містить байт 0xa. У цьому випадку ви можете використовувати інший роздільник записів (з -0ooo).
Стефан Шазелас

1
@ StéphaneChazelas ви можете використовувати саму послідовність, що представляє інтерес $/, з дещо іншим компромісом (використання пам'яті пропорційне максимальній відстані між такими послідовностями):perl -nE 'BEGIN { $/ = "\xef\xbe\xad\xde" } chomp; $c++ unless eof && length; END { say $c }'
hobbs

@ StéphaneChazelas Будь ласка, прочитайте мою відповідь на вирішення будь-яких значень байтів.
соронтар

1
@hobbs, у будь-якому випадку, навіть тут використання пам'яті буде пропорційним максимальній відстані між двома байтами 0xa, які для нетекстових файлів можуть бути довільно великими.
Стефан Шазелас

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-цитування (спочатку ksh93функції, тепер підтримується zsh, bash, mksh, FreeBSD sh) версія риби \Xef, і використовується grep -o ... | wc -lдля підрахунку екземплярів. grep -oвиводить кожну відповідність в окремий рядок. -aПрапор робить Grep себе на бінарні файли так само , як це робить на текстові файли. -Fпризначений для фіксованих рядків, тому вам не потрібно уникати операторів регулярних виразів.

Як і у вашому fishвипадку, ви не можете використовувати такий підхід, хоча послідовність пошуку включає байти 0 або 0xa (новий рядок в ASCII).


Використання printf '%b' $(printf '\\%o ' $((0xef)) $((0xbe)) $((0xad)) $((0xde))) > hugohex'було б найбільш портативним методом "чистої оболонки". Звичайно: printf "efbeadde" | xxd -p -r > hugohexздається найбільш практичним методом.
соронтар

4

Ви можете скористатися bytes.countметодом Python, щоб отримати загальну кількість підкладок, що не перетинаються, у бітестрінгу.

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я не повністю зрозумів, але не працює без виходу - з Документи:

змушує Perl приймати наступний цикл навколо вашої програми, що змушує ітерацію аргументів імені файлу дещо нагадувати sed -n чи awk: LINE: while (<>) {... # ваша програма переходить сюди}

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.