Бинарный grep для Linux?
Скажем, я создал следующий двоичный файл:
# generate file:
python -c 'import sys;[sys.stdout.write(chr(i)) for i in (0,0,0,0,2,4,6,8,0,1,3,0,5,20)]' > mydata.bin
# get file size in bytes
stat -c '%s' mydata.bin
# 14
И скажите, я хочу найти места всех нулей (0x00
), используя синтаксис grep-like.
Лучшее, что я могу сделать до сих пор:
$ hexdump -v -e "1/1 \" %02x\n\"" mydata.bin | grep -n '00'
1: 00
2: 00
3: 00
4: 00
9: 00
12: 00
Однако это неявно преобразует каждый байт в исходном двоичном файле в многобайтовое представление ASCII, на котором работает grep
; не совсем лучший пример оптимизации:)
Есть ли что-то вроде двоичного grep
для Linux? Возможно, также, что-то, что будет поддерживать регулярный синтаксис типа выражения, но также и для байтовых "символов", то есть я мог бы написать что-то вроде "a(\x00*)b
" и сопоставлять "ноль или более" вхождения байта 0 между байтами ' a '(97) и' b '(98)?
EDIT: контекст заключается в том, что я работаю над драйвером, где я беру 8-битные данные; что-то не получается в данных, которые могут быть килобайтами до мегабайт, и я бы хотел проверить конкретные подписи и где они происходят. (до сих пор я работаю с фрагментами килобайта, поэтому оптимизация не так важна, но если я начну получать некоторые ошибки в мегабайтных длинных захватах, и мне нужно проанализировать их, я думаю, что я хотел бы что-то более оптимизированное:). И особенно, мне хотелось бы что-то, где я могу "grep" для байта как персонажа - hexdump
заставляет меня искать строки за каждый байт)
EDIT2: тот же вопрос, другой форум:) grepping через двоичный файл для последовательности байтов
EDIT3: благодаря ответу @tchrist, вот пример с "grepping" и совпадением, и отображение результатов (хотя и не совсем такой же вопрос, как OP):
$ perl -ln0777e 'print unpack("H*",$1), "\n", pos() while /(.....\0\0\0\xCC\0\0\0.....)/g' /path/to/myfile.bin
ca000000cb000000cc000000cd000000ce # Matched data (hex)
66357 # Offset (dec)
Чтобы согласованные данные группировались как один байт (два шестнадцатеричных символа) каждый, тогда необходимо указать "H2 H2 H2..." для количества байтов в согласованной строке; так как мой матч ".....\0\0\0\xCC\0\0\0.....
" охватывает 17 байтов, я могу написать ""H2"x17
" в Perl. Каждая из этих "H2" вернет отдельную переменную (как в списке), поэтому join
также необходимо использовать для добавления пробелов между ними - в конце концов:
$ perl -ln0777e 'print join(" ", unpack("H2 "x17,$1)), "\n", pos() while /(.....\0\0\0\xCC\0\0\0.....)/g' /path/to/myfile.bin
ca 00 00 00 cb 00 00 00 cc 00 00 00 cd 00 00 00 ce
66357
Ну.. действительно, Perl очень хороший объект двоичного grepping, я должен признать:) До тех пор, пока вы правильно изучаете синтаксис:)
Ответы
Ответ 1
Вход с одним лайнером
Вот более короткая версия с одним слоем:
% perl -ln0e 'print tell' < inputfile
И вот немного длинный однострочный:
% perl -e '($/,$\) = ("\0","\n"); print tell while <STDIN>' < inputfile
Способ подключения этих двух однострочных устройств состоит в том, чтобы скомпилировать первую программу:
% perl -MO=Deparse,-p -ln0e 'print tell'
BEGIN { $/ = "\000"; $\ = "\n"; }
LINE: while (defined(($_ = <ARGV>))) {
chomp($_);
print(tell);
}
Запрограммированный вход
Если вы хотите поместить это в файл вместо вызова из командной строки, это будет несколько более явная версия:
#!/usr/bin/env perl
use English qw[ -no_match_vars ];
$RS = "\0"; # input separator for readline, chomp
$ORS = "\n"; # output separator for print
while (<STDIN>) {
print tell();
}
И есть очень длинная версия:
#!/usr/bin/env perl
use strict;
use autodie; # for perl5.10 or better
use warnings qw[ FATAL all ];
use IO::Handle;
IO::Handle->input_record_separator("\0");
IO::Handle->output_record_separator("\n");
binmode(STDIN); # just in case
while (my $null_terminated = readline(STDIN)) {
# this just *past* the null we just read:
my $seek_offset = tell(STDIN);
print STDOUT $seek_offset;
}
close(STDIN);
close(STDOUT);
Выход с одним лайнером
BTW, чтобы создать тестовый входной файл, я не использовал ваш большой, длинный Python script; Я просто использовал этот простой однострочный Perl:
% perl -e 'print 0.0.0.0.2.4.6.8.0.1.3.0.5.20' > inputfile
Вы обнаружите, что Perl часто заканчивается в 2-3 раза короче, чем Python, чтобы выполнять ту же работу. И вам не нужно идти на компромисс по ясности; что может быть проще, чем однострочный выше?
Запрограммированный выход
Я знаю, я знаю. Если вы уже не знаете язык, это может быть яснее:
#!/usr/bin/env perl
@values = (
0, 0, 0, 0, 2,
4, 6, 8, 0, 1,
3, 0, 5, 20,
);
print pack("C*", @values);
хотя это тоже работает:
print chr for @values;
как и
print map { chr } @values;
Хотя для тех, кто любит все, все строгие и осторожные и все, это может быть больше того, что вы увидите:
#!/usr/bin/env perl
use strict;
use warnings qw[ FATAL all ];
use autodie;
binmode(STDOUT);
my @octet_list = (
0, 0, 0, 0, 2,
4, 6, 8, 0, 1,
3, 0, 5, 20,
);
my $binary = pack("C*", @octet_list);
print STDOUT $binary;
close(STDOUT);
TMTOWTDI
Perl поддерживает несколько способов сделать что-то, чтобы вы могли выбрать тот, который вам больше всего нравится. Если бы это было что-то, что я планировал проверить в качестве школьного или рабочего проекта, я бы выбрал более длинные, более тщательные версии - или, по крайней мере, поместил комментарий в оболочку script, если бы я использовал однострочные.
Вы можете найти документацию для Perl в своей собственной системе. Просто введите
% man perl
% man perlrun
% man perlvar
% man perlfunc
и т.д. в командной строке. Если вы хотите получить симпатичные версии в Интернете вместо этого, получите manpages для perl, perlrun, perlvar и perlfunc из http://perldoc.perl.org.
Ответ 2
Кажется, это работает для меня:
grep --only-matching --byte-offset --binary --text --perl-regexp "<\x-hex pattern>" <file>
Краткая форма:
grep -obUaP "<\x-hex pattern>" <file>
Пример:
grep -obUaP "\x01\x02" /bin/grep
Вывод (Cygwin двоичный файл):
153: <\x01\x02>
33210: <\x01\x02>
53453: <\x01\x02>
Таким образом, вы можете снова выполнить эту операцию, чтобы извлечь смещения. Но не забудьте снова использовать двоичный режим.
Ответ 3
Кто-то, похоже, был так же расстроен и написал свой собственный инструмент для этого (или, по крайней мере, что-то подобное): bgrep.
Ответ 4
Программа bbe - это sed -образный редактор для двоичных файлов. См. документация.
Пример с bbe:
bbe -b "/\x00\x00\xCC\x00\x00\x00/:17" -s -e "F d" -e "p h" -e "A \n" mydata.bin
11:x00 x00 xcc x00 x00 x00 xcd x00 x00 x00 xce
Объяснение
-b search pattern between //. each 2 byte begin with \x (hexa notation).
-b works like this /pattern/:length (in byte) after matched pattern
-s similar to 'grep -o' suppress unmatched output
-e similar to 'sed -e' give commands
-e 'F d' display offsets before each result here: '11:'
-e 'p h' print results in hexadecimal notation
-e 'A \n' append end-of-line to each result
Вы также можете передать его в sed, чтобы получить более чистый результат:
bbe -b "/\x00\x00\xCC\x00\x00\x00/:17" -s -e "F d" -e "p h" -e "A \n" mydata.bin | sed -e 's/x//g'
11:00 00 cc 00 00 00 cd 00 00 00 ce
Ваше решение с Perl из вашего EDIT3 дает мне "недостаток памяти", ошибка с большими файлами.
То же самое происходит с bgrep.
Единственным недостатком bbe является то, что я не знаю, как печатать контекст, предшествующий сопоставленному шаблону.
Ответ 5
Один из способов решения вашей непосредственной проблемы с использованием только grep - создать файл, содержащий один нулевой байт. После этого grep -abo -f null_byte_file target_file
выдаст следующий результат.
0:
1:
2:
3:
8:
11:
Это, конечно, каждое смещение байта в соответствии с запросом "-b", за которым следует нулевой байт по запросу "-o"
Я буду первым защищать perl, но в этом случае нет необходимости вводить расширенную семью.
Ответ 6
Как насчет grep -a
? Не уверен, как он работает на действительно двоичных файлах, но он хорошо работает в текстовых файлах, которые ОС считает двоичными.