web-dev-qa-db-fra.com

Grep binaire sur Linux?

Supposons que j'ai généré le fichier binaire suivant:

# 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

Et disons, je veux trouver les emplacements de tous les zéros (0x00), En utilisant une syntaxe de type grep.

Le mieux que je puisse faire jusqu'à présent est:

$ hexdump -v -e "1/1 \" %02x\n\"" mydata.bin | grep -n '00'

1: 00
2: 00
3: 00
4: 00
9: 00
12: 00

Cependant, cela convertit implicitement chaque octet du fichier binaire d'origine en une représentation multi-octets ASCII, sur laquelle grep fonctionne; pas exactement le premier exemple d'optimisation :)

Existe-t-il quelque chose comme un grep binaire pour Linux? Peut-être aussi quelque chose qui prendrait en charge une syntaxe de type expression régulière, mais aussi pour les "caractères" d'octets - c'est-à-dire que je pourrais écrire quelque chose comme 'a(\x00*)b' et faire correspondre les occurrences d'octet 'zéro ou plus' 0 entre les octets 'a' (97) et 'b' (98)?

EDIT: Le contexte est que je travaille sur un pilote, où je capture des données 8 bits; quelque chose ne va pas dans les données, qui peuvent aller de plusieurs kilo-octets à des mégaoctets, et j'aimerais vérifier des signatures particulières et leur emplacement. ( jusqu'à présent, je travaille avec des extraits de kilo-octets, donc l'optimisation n'est pas si importante - mais si je commence à obtenir des erreurs dans les captures longues de mégaoctets, et je dois les analyser, ma conjecture est Je voudrais quelque chose de plus optimisé :). Et surtout, je voudrais quelque chose où je peux "grep" pour un octet en tant que caractère - hexdump me force à rechercher des chaînes par octet )

EDIT2: même question, forum différent :) en parcourant un fichier binaire pour une séquence d'octets

EDIT3: Grâce à la réponse de @tchrist, voici également un exemple avec 'grepping' et correspondance, et affichant les résultats ( bien que ce ne soit pas tout à fait la même question que 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)

Pour que les données correspondantes soient regroupées en un octet (deux caractères hexadécimaux) chacune, alors "H2 H2 H2 ..." doit être spécifié car il y a autant d'octets dans la chaîne correspondante; comme ma correspondance '.....\0\0\0\xCC\0\0\0.....' couvre 17 octets, je peux écrire '"H2"x17' en Perl. Chacun de ces "H2" renverra une variable distincte (comme dans une liste), donc join doit également être utilisé pour ajouter des espaces entre eux - éventuellement:

$ 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

Eh bien .. en effet Perl est une installation très agréable de "grepping binaire", je dois admettre :) Tant qu'on apprend la syntaxe correctement :)

28
sdaau

Entrée à une ligne

Voici la version monoplace plus courte:

% Perl -ln0e 'print tell' < inputfile

Et voici une doublure légèrement plus longue:

% Perl -e '($/,$\) = ("\0","\n"); print tell while <STDIN>' < inputfile

La façon de connecter ces deux lignes simples consiste à décompiler le programme du premier:

% Perl -MO=Deparse,-p -ln0e 'print tell'
BEGIN { $/ = "\000"; $\ = "\n"; }
LINE: while (defined(($_ = <ARGV>))) {
    chomp($_);
    print(tell);
}

Entrée programmée

Si vous voulez mettre cela dans un fichier au lieu de l'appeler depuis la ligne de commande, voici une version un peu plus explicite:

#!/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();
}

Et voici la version vraiment longue:

#!/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);

Sortie à une ligne

BTW, pour créer le fichier d'entrée de test, je n'ai pas utilisé votre gros, long Python; je viens d'utiliser cette simple ligne Perl:

% Perl -e 'print 0.0.0.0.2.4.6.8.0.1.3.0.5.20' > inputfile

Vous constaterez que Perl finit souvent par être 2 à 3 fois plus court que Python pour faire le même travail. Et vous n'avez pas à faire de compromis sur la clarté; quoi de plus simple que celui-là -liner ci-dessus?

Sortie programmée

Je sais je sais. Si vous ne connaissez pas déjà la langue, cela pourrait être plus clair:

#!/usr/bin/env Perl
@values = (
    0,  0,  0,  0,  2,
    4,  6,  8,  0,  1,
    3,  0,  5, 20,
);
print pack("C*", @values);

bien que cela fonctionne aussi:

print chr for @values;

de même que

print map { chr } @values;

Bien que pour ceux qui aiment tout tout rigoureux et prudent et tout, cela pourrait être plus ce que vous verriez:

#!/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 prend en charge plusieurs méthodes pour que vous puissiez choisir celle avec laquelle vous êtes le plus à l'aise. Si c'était quelque chose que j'avais prévu de m'enregistrer en tant que projet scolaire ou professionnel, je choisirais certainement les versions plus longues et plus prudentes - ou au moins mettrais un commentaire dans le script Shell si j'utilisais les lignes simples.

Vous pouvez trouver de la documentation pour Perl sur votre propre système. Tapez simplement

% man Perl
% man perlrun
% man perlvar
% man perlfunc

etc à votre invite Shell. Si vous souhaitez plutôt des versions plutôt jolies sur le Web, obtenez les pages de manuel pour Perl , perlrun , perlvar et perlfunc de http://perldoc.Perl.org .

14
tchrist

Cela semble fonctionner pour moi:

grep --only-matching --byte-offset --binary --text --Perl-regexp "<\x-hex pattern>" <file>

Forme courte:

grep -obUaP "<\x-hex pattern>" <file>

Exemple:

grep -obUaP "\x01\x02" /bin/grep

Sortie ( Cygwin binaire):

153: <\x01\x02>
33210: <\x01\x02>
53453: <\x01\x02>

Vous pouvez donc à nouveau le récupérer pour extraire les décalages. Mais n'oubliez pas d'utiliser à nouveau le mode binaire.

44
Fr0sT

Quelqu'un d'autre semble avoir été frustré de la même manière et a écrit son propre outil pour le faire (ou au moins quelque chose de similaire): bgrep .

19
David Dean

Le programme bbe est un éditeur de type sed pour les fichiers binaires. Voir documentation .

Exemple avec 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

Explication

-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

Vous pouvez également le diriger vers sed pour avoir une sortie plus propre:

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

Votre solution avec Perl de votre EDIT3 me donne une erreur "Mémoire insuffisante" avec des fichiers volumineux.

Le même problème se pose avec bgrep .

Le seul inconvénient de bbe est que je ne sais pas comment imprimer le contexte qui précède un motif correspondant.

10
hdorio

Une façon de résoudre votre problème immédiat en utilisant uniquement grep est de créer un fichier contenant un seul octet nul. Après ça, grep -abo -f null_byte_file target_file produira la sortie suivante.

 0: 
 1: 
 2: 
 3: 
 8: 
 11: 

C'est bien sûr chaque octet décalé comme demandé par "-b" suivi d'un octet nul comme demandé par "-o"

Je serais le premier à défendre Perl, mais dans ce cas, il n'est pas nécessaire de faire venir la famille élargie.

8
Omniwombat

Qu'en est-il de grep -a? Je ne sais pas comment cela fonctionne sur les fichiers vraiment binaires, mais cela fonctionne bien sur les fichiers texte que le système d'exploitation pense être binaires.

1
Chance