Использование grep для извлечения очень специфических c строк из двоичного файла - PullRequest
1 голос
/ 03 февраля 2020

У меня большой двоичный файл. Я хочу извлечь из него определенные строки и скопировать их в новый текстовый файл.

Например, в:

D-wM-^?^@^@^@^@^@^@^@^Y^@^@^@^@^@^@^@M-lM-FM-MM-[o@^B^@M-lM-FM MM-[o@^B^@^@^@^@^@E7cacscKLrrok9bwC3Z64NTnZM-^G

Я хочу взять число «7» (после @^@^@E) и каждый символ после него останавливается на Z ('игнорируя M-^G).

Я хочу скопировать 7cacscKLrrok9bwC3Z64NTnZ в новый файл.

Там будет быть несколько таких строк в одном файле. Конец всегда будет обозначаться M- (который я не хочу копировать). Начало всегда будет обозначаться 7 (который я хочу скопировать).

К сожалению, мои знания по grep, sed и др. c не распространяются на этот уровень. Может кто-нибудь, пожалуйста, предложить жизнеспособный способ достижения этого?

cat -v filename | grep [7][A-Z,a-z] покажет все строки с «7», за которым следует буква, но это не так много.

Спасибо.


Я заметил, что мои требования довольно сложны.

(на этот раз я выполнил правильное, надеюсь, форматирование). Спасибо 'tshiono' за его (?) Ответ на предыдущую отправку.

Я хочу проверить окончание строки и, если она заканчивается в M-, grep другой строки, которая следует за ней (с мусором между). Если строка не заканчивается на M-, то я не хочу, чтобы она копировалась (не говоря уже о любых других строках).

Итак, я бы хотел:

grep -a -Po "7[[:alnum:]]+(?=M-)" file_name и если окончание M-, то grep -a -Po "5x[[:alnum:]]+(?=\^)" file_name для копирования строки, начинающейся с 5x и заканчивающейся ^.

В этом примере:

D-wM-^?^@^@^@^@^@^@^@^Y^@^@^@^@^@^@^@M-lM-FM-MM-[o@^B^@M-lM-FM MM-[o@^B^@^@^@^@^@E7cacscKLrrok9bwC3Z64NTnZM-^GwM-^?^@^@^@^@^@^@^@^Y^@^@^@^@^@^@^@M-lM-FM-MM-[o@^B^@M-lM5x8w09qewqlkcklwnlkewflewfiewjfoewnflwenfwlkfwelk^89038432nowefe

Результат будет:

7cacscKLrrok9bwC3Z64NTnZ
5x8w09qewqlkcklwnlkewflewfiewjfoewnflwenfwlkfwelk

Однако, если окончание не M- (точнее, если окончание ^S), то не пытайтесь использовать второй grep и вообще ничего не записывать.

В этом примере:

D-wM-^?^@^@^@^@^@^@^@^Y^@^@^@^@^@^@^@M-lM-FM-MM-[o@^B^@M-lM-FM MM-[o@^B^@^@^@^@^@E7cacscKLrrok9bwC3Z64NTnZ^SGwM-^?^@^@^@^@^@^@^@^Y^@^@^@^@^@^@^@M-lM-FM-MM-[o@^B^@M-lM5x8w09qewqlkcklwnlkewflewfiewjfoewnflwenfwlkfwelk^89038432nowefe

Результат будет нулевым (ничего не копируется), поскольку строка 7cacs... заканчивается на ^S.

Является ли grep правильным орудие труда? Grep файл, и если условие в команде grep «да», тогда введите другую команду grep, но если условие «нет», то ничего не делайте.

Еще раз спасибо.


Я заметил одну дополнительную модификацию.

Можно ли добавить команду ИЛИ во вторую часть? Grep, если вторая строка начинается с 5x ИЛИ 6x?

В приведенном ниже примере grep -aPo "7[[:alnum:]]+M-.*?5x[[:alnum:]]+\^" filename | grep -aPo "7[[:alnum:]]+(?=M-)|5x[[:alnum:]]+(?=\^)" извлечет строки, начинающиеся с 7, и строки, начинающиеся с 5x.

Как можно изменить 5x на 5x или 6x? ​​

D-wM-^?^@^@^@^@^@^@^@^Y^@^@^@^@^@^@^@M-lM-FM-MM-[o@^B^@M-lM-FM MM-[o@^B^@^@^@^@^@E7cacscKLrrok9bwC3Z64NTnZM-^GwM-^?^@^@^@^@^@^@^@^Y^@^@^@^@^@^@^@M-lM-FM-MM-[o@^B^@M-lM5x8w09qewqlkcklwnlkewflewfiewjfoewnflwenfwlkfwelk^89038432nowefe
D-wM-^?^@^@^@^@^@^@^@^Y^@^@^@^@^@^@^@M-lM-FM-MM-[o@^B^@M-lM-FM MM-[o@^B^@^@^@^@^@E7AAAAAscKLrrok9bwC3Z64NTnZM-^GwM-^?^@^@^@^@^@^@^@^Y^@^@^@^@^@^@^@M-lM-FM-MM-[o@^B^@M-lM6x8w09qewqlkcklwnlkewflewfiewjfoewnflwenfwlkfwelk^89038432nowefe

В этом примере желаемый результат будет следующим:

7cacscKLrrok9bwC3Z64NTnZ
5x8w09qewqlkcklwnlkewflewfiewjfoewnflwenfwlkfwelk
7AAAAAscKLrrok9bwC3Z64NTnZ
6x8w09qewqlkcklwnlkewflewfiewjfoewnflwenfwlkfwelk

ОБНОВЛЕНИЕ МАРТА 09:

Мне нужно создать серию сложные команды grep (или perl) для извлечения строк из серии двоичных файлов.

Мне нужны две строки из двоичного файла.

Первая строка всегда начинается с 1.

Первая строка заканчивается буквой или цифрой. Следующая буква всегда будет строчной k. Мне не нужен этот k символ.

Сложность в том, что окончание k не всегда будет первым k в строке. Это может быть первый k, но это не так.

После k появляется вторая строка. Вторая строка всегда будет начинаться с A или B.

Окончание второй строки будет иметь одну из двух форм: a) она будет заканчиваться пробелом, а затем отображать первые три символы из первой строки в нижнем регистре, за которыми следует ) b) он заканчивается ^K, затем отображаются первые три символа из первой строки в нижнем регистре.

Например:

1pppsx9YPar8Rvs75tJYWZq3eo8PgwbckB4m4zT7Yg042KIDYUE82e893hY ppp)

Должно быть:

1pppsx9YPar8Rvs75tJYWZq3eo8Pgwbc и B4m4zT7Yg042KIDYUE82e893hY - удалить k и затем пробел ppp.

Например :

1zzzsx9YPkr8Rvs75tJYWZq3eo8PgwbckA2m4zT7Yg042KIDYUE82e893hY^Kzzz

Должно быть:

1zzzsx9YPkar8Rvs75tJYWZq3eo8Pgwbc и A4m4zT7Yg042KIDYUE82e893hY - удалить второе k и ^Kzzz.

Во втором примере мы видим, что первый k является частью первой строки. Это k перед A, которая разбивает первую и вторую строки.

Я надеюсь, что есть эксперт по супер grep, который может помочь! Большое спасибо!

Ответы [ 3 ]

4 голосов
/ 03 февраля 2020

Если ваш grep поддерживает опцию -P, попробуйте:

grep -a -Po "7[[:alnum:]]+(?=M-)" file
  • Опция -a заставляет grep прочитать ввод как текстовый файл.
  • Опция -P включает perl -совместимое регулярное выражение.
  • Опция -o указывает grep печатать только совпадающие подстроки.
  • Шаблон (?=M-) является утверждением предпросмотра нулевой ширины (представлен в Perl) без включения его в результат.

В качестве альтернативы вы также можете сказать с помощью sed:

sed 's/M-/\n/g' file | sed -n 's/.*\(7[[:alnum:]]\+\).*/\1/p'
  • Первая команда sed разбивает входной файл на несколько строк, заменяя подстроку M- новой строкой. Он имеет два преимущества: он разбивает строки, чтобы разрешить множественные совпадения с sed, и исключает ненужную часть M- из входных данных.
  • Следующая команда sed извлекает желаемый шаблон из входных данных.

Предполагается, что ваш sed принимает \n в замену, которая является расширением GNU ( не POSIX-совместимый). В противном случае, попробуйте (в случае, если вы работаете с bash):

sed 's/M-/\'$'\n''/g' file | sed -n 's/.*\(7[[:alnum:]]\+\).*/\1/p'

[ОБНОВЛЕНИЕ]
(Требование было обновлено OP, и следующие решения являются решениями) в соответствии с ним.)

Позвольте мне предположить, что строка, начинающаяся с 7 и заканчивающаяся M-, равна всегда , за которой следует другая (не более и не менее одной) строка, которая начинается с 5x и заканчивается ^ (символ каретки ascii) с джонками между ними.
Тогда попробуйте следующее:

grep -aPo "7[[:alnum:]]+M-.*?5x[[:alnum:]]+\^" file | grep -aPo "7[[:alnum:]]+(?=M-)|5x[[:alnum:]]+(?=\^)"
  • Выполняет задачу в два шаги (два каскадных greps).
  • 1-й grep сужает входные данные в подстроку-кандидату, которая будет включать в себя две нужные последовательности и джанксы между ними.
  • Регулярное выражение .*? между ними соответствует любым (ascii или двоичным) символам, кроме символа новой строки. Трейлинг ? включает shortest match, что позволяет избежать переполнения из-за природы greedy регулярного выражения. Регулярное выражение предназначено для сопоставления джонков между ними.
  • 2-й grep включает два регулярных выражения, объединенных с трубкой |, что означает логический OR. Затем он извлекает две требуемые последовательности.

Потенциальная проблема решения grep заключается в том, что grep является строкой, ориентированной на команду, и не может включать символ новой строки в совпавшей строке. Если символ новой строки включен в junks in between (я не уверен насчет возможности), вышеуказанное решение не будет выполнено. В качестве обходного пути, perl обеспечит гибкие манипуляции с двоичными данными.

perl -0777 -ne '
    while (/(7[[:alnum:]]+)M-.*?(5x[[:alnum:]]+)\^/sg) {
        printf("%s\n%s\n", $1, $2);
    }
' file
  • Регулярное выражение в основном такое же, как у grep, поскольку опция -P для grep означает perl -совместим.
  • Может захватывать несколько шаблонов одновременно в переменных $1 и $2, поэтому достаточно одного регулярного выражения.
  • Опция -0777 для perl Команда perl указывает, чтобы все данные были отброшены за один раз.
  • Опция s в конце регулярного выражения ставит точку в соответствие с символом новой строки.
  • Опция g включает global (несколько) совпадений.

[ОБНОВЛЕНИЕ2]
Чтобы сделать регулярное выражение равным 5x или 6x, замените 5x на (5|6)x.
А именно:

grep -aPo "7[[:alnum:]]+M-.*?(5|6)x[[:alnum:]]+\^" file | grep -aPo "7[[:alnum:]]+(?=M-)|(5|6)x[[:alnum:]]+(?=\^)"

Как упоминалось ранее, труба | означает OR. Оператор OR имеет наименьший приоритет в оценке, поэтому в этом случае вам необходимо заключить их в скобки.

Если есть вероятность, что может появиться любое другое число, кроме 5 или 6, это будет безопаснее вместо этого ставить [[:digit:]], что соответствует любому ди git между 0 и 9:

grep -aPo "7[[:alnum:]]+M-.*?[[:digit:]]x[[:alnum:]]+\^" file | grep -aPo "7[[:alnum:]]+(?=M-)|[[:digit:]]x[[:alnum:]]+(?=\^)"

[ОБНОВЛЕНИЕ3]
(отвечая на требование ОП 9 марта)

Позвольте мне начать с perl кода, регулярное выражение которого будет относительно легко объяснить.

perl -0777 -ne 'while (/(1(.{3}).+)k([AB].*)[\013 ]\2/g){print "$1 $3\n"}' file

Вывод:

1pppsx9YPar8Rvs75tJYWZq3eo8Pgwbc B4m4zT7Yg042KIDYUE82e893hY
1zzzsx9YPkr8Rvs75tJYWZq3eo8Pgwbc A2m4zT7Yg042KIDYUE82e893hY

[Объяснение regex]

(1(.{3}).+)k([AB].*)[\013 ]\2
(                  start of the 1st capture group referred by $1 later
 1                 literal "1"
  (                start of the 2nd capture group referred by \2 later
   .{3}            a sequence of the identical three characters such as ppp or zzz
       )           end of the 2nd capture group
        .+         followed by any characters with "greedy" match which may include the 1st "k"
          )        end of the 1st capture group
           k       literal "k"
(                  start of the 3rd capture group referred by $3 later
 [AB].*            the character "A" or "B" followed by any characters
       )           end of the 3rd capture group
        [\013 ]    followed by ^K or a whitespace
               \2  followed by the capture group 2 previously assigned

При реализации с grep мы столкнемся с ограничением grep. Хотя мы хотим извлечь несколько шаблонов из входного файла, опция -e (которая может указывать несколько шаблонов поиска) не работает с опцией -P. Затем нам нужно разделить регулярное выражение на два шаблона, таких как:

grep -Po "(1(.{3}).+)(?=k([AB].*)[\013 ]\2)" file
grep -Po "(1(.{3}).+)k\K([AB].*)(?=[\013 ]\2)" file

И результат будет:

1pppsx9YPar8Rvs75tJYWZq3eo8Pgwbc
1zzzsx9YPkr8Rvs75tJYWZq3eo8Pgwbc
B4m4zT7Yg042KIDYUE82e893hY
A2m4zT7Yg042KIDYUE82e893hY

Обратите внимание, порядок вывода не совпадает с порядком появления в исходном файле.

Другой вариант - ввести ripgrep или rg, которая является быстрой и универсальной версией grep. Вам может понадобиться установить ripgrep с sudo apt install ripgrep или с помощью другого инструмента обработки пакетов. Преимущество ripgrep в том, что он поддерживает опцию -r (замена), в которой вы можете использовать обратные ссылки:

rg -N -Po "(1(.{3}).+)k([AB].*)[\013 ]\2" -r '$1 $3' file

Опция -r '$1 $3' печатает 1-ю и 3-ю группы захвата и результат будет таким же, как perl.

1 голос
/ 03 февраля 2020

В общем случае вы можете использовать утилиту strings для извлечения ASCII из двоичных файлов; тогда, конечно, вы можете попробовать grep этот вывод для шаблонов, которые вам интересны.

Многие традиционные Unix утилиты, такие как grep, имеют внутренние специальные маркеры, которые могут быть испорчены двоичным вводом. Например, символ \ xFF использовался для внутренних целей некоторыми версиями GNU grep, поэтому вы не можете grep для этого символа, даже если вы можете найти способ представить его в оболочке (Bash поддерживает $'\xff' например).

Традиционным подходом будет запуск hexdump или аналогичной утилиты, а затем grep для шаблонов. Однако более современные языки сценариев, такие как Perl и Python, упрощают манипулирование произвольными двоичными данными.

perl -ne 'print if m/\xff\xff/' </dev/urandom
0 голосов
/ 03 февраля 2020

Это может работать для вас (GNU sed):

sed -En '/\n/!{s/M-\^G/\n/;s/7[^\n]*\n/\n&/};/^7[^\n]*/P;D' file

Разделить каждую строку на ноль или более строк, начинающихся с 7 и заканчивающихся непосредственно перед M-^G, и печатать только такие строки.

...