Comment inverser le contenu d'un fichier binaire?

11

Je résolvais un défi où j'ai trouvé un fichier de données sans extension de fichier. La filecommande montre qu'il s'agit d'un data file (application/octet-stream). La hdcommande affiche GNP. dans la dernière ligne. Donc, si j'inverse ce fichier, j'obtiendrai le fichier au format .PNG , j'ai cherché partout mais je n'ai pas trouvé de solution expliquant comment inverser le contenu d'un fichier binaire.

Prvt_Yadav
la source

Réponses:

11

Avec xxd(de vim) et tac(de GNU coreutils, également tail -rsur certains systèmes):

< file.gnp xxd -p -c1 | tac | xxd -p -r > file.png
Stéphane Chazelas
la source
Existe-t-il un moyen de combiner cela avec vi.stackexchange.com/a/2237/10649 ? J'ai essayé toutes sortes de combinaisons sans succès :(
Iulian Onofrei
Ce n'est pas une solution car elle reflétera tout le fichier.
Philippe Delteil
@PhilippeDelteil, refléter tout le fichier était ce que l'OP demande ici? Que voudriez-vous qu'il fasse d'autre?
Stéphane Chazelas
4

Dans zsh(le seul shell capable de traiter en interne les données binaires (sauf si vous voulez considérer l'approche de codage base64 de ksh93 )):

zmodload zsh/mapfile
(LC_ALL=C; printf %s ${(s::Oa)mapfile[file.gnp]} > file.png)
  • LC_ALL=C: les caractères sont des octets
  • $mapfile[file.gnp]: contenu du file.gnpfichier
  • s::: divise la chaîne en ses constituants d'octet
  • Oa: inverser Ole arder sur l' indice de rray ce tableau
Stéphane Chazelas
la source
1
zshn'est pas le seul shell capable de gérer des données binaires.
fpmurphy
2

Voici une façon d'inverser un fichier binaire en utilisant ksh93. J'ai laissé le code "lâche" pour le rendre plus facile à comprendre.

#!/bin/ksh93

typeset -b byte

redirect 3< image.gpj || exit 1

eof=$(3<#((EOF)))

read -r -u 3 -N 1 byte
printf "%B" byte > image.jpg
3<#((CUR - 1))

while (( $(3<#) > 0 ))
do
    read -r -u 3 -N 1 byte
    printf "%B" byte >> image.jpg
    3<#((CUR - 2))
done

read -r -u 3 -N 1 byte
printf "%B" byte >> image.jpg

redirect 3<&- || echo 'cannot close FD 3'

exit 0
fpmurphy
la source
agréable. C'est la seule réponse à ce jour qui n'implique pas de stocker tout le fichier en mémoire. Cependant, il est terriblement inefficace dans la mesure où il effectue plusieurs appels système pour chaque octet du fichier (et des conversions vers / depuis base64), il ne conviendrait donc pas non plus aux fichiers qui ne tiennent pas en mémoire. Sur ma machine, il traite les fichiers à environ 10Ko / s
Stéphane Chazelas
Notez que le premier readci-dessus ne devrait rien lire car il se fait à la fin du fichier.
Stéphane Chazelas
En essayant de comprendre pourquoi il était si lent, j'ai essayé de l'exécuter sous straceet ksh93semble se comporter très bizarrement, où il cherche partout dans le fichier et lit de grandes quantités à l'époque. Peut-être une variante de github.com/att/ast/issues/15
Stéphane Chazelas
@ StéphaneChazelas. Pas de mystère pour expliquer pourquoi il est relativement lent. Dans la boucle, il doit chercher en arrière chaque fois qu'il lit un octet. Cela peut facilement être considérablement réduit d'un facteur 20 ou même plus en lisant et en écrivant plus d'un octet à la fois. Le côté écriture des choses peut également être optimisé. De nombreuses autres techniques sont disponibles pour accélérer encore les choses. Je vous laisse cet exercice.
fpmurphy
Essayez stracele script pour voir ce que je veux dire. ksh93lit les fichiers des milliers de fois. Par exemple, avant de lire le premier octet, il recherche 64 Ko à la fin du fichier, lit 64 Ko, puis recherche avant le dernier octet et lit 1 octet et fait quelque chose de similaire pour chaque octet. Notez que ce que vous pouvez faire avec ces chaînes encodées en base64 est limité, donc si vous lisez plus d'un octet à la fois, il sera plus difficile d'extraire les octets individuels de cela.
Stéphane Chazelas
2

Avec perl:

perl -0777pe '$_=reverse $_'  [input_file]

Test de performance:

dd if=/dev/urandom of=/tmp/a bs=1M count=1
LC_ALL=C tac -rs $'.\\|\n' /tmp/a > /tmp/r

time perl -0777pe '$_=reverse $_' /tmp/a         | diff -q - /tmp/r
time xxd -p -c1 /tmp/a | tac | xxd -p -r         | diff -q - /tmp/r
time perl -0777 -F -ape '$_=reverse@F' /tmp/a    | diff -q - /tmp/r
time LC_ALL=C tac -rs $'.\\|\n' /tmp/a           | diff -q - /tmp/r

Résultat:

  • Testé localement: ma solution est la plus rapide, perl -0777 -Fla plus lente.
  • Testé sur Essayez-le en ligne! : ma solution est la plus rapide, xxdest la plus lente.

Remarque: les délais diffdoivent être les mêmes pour toutes les solutions, car la sortie doit être la même.

user202729
la source
1
J'ai supprimé le mien perl. Je n'avais pas réalisé à l'époque que reverseje pouvais aussi inverser les chaînes, donc le fractionnement n'avait pas beaucoup de sens et votre version est bien meilleure.
Stéphane Chazelas
1

J'ai essayé ce qui suit:

tac -rs '.' input.gnp > output.png

L'idée est de forcer 'tac' en utilisant n'importe quel caractère comme séparateur. J'ai essayé cela sur un fichier binaire et cela semblait fonctionner mais toute confirmation serait appréciée.

Le principal avantage est qu'il ne charge pas le fichier en mémoire.

Bouteille
la source
Ne fonctionne pas pour moi (ici avec GNU tac8.28) lorsque l'entrée contient des caractères de nouvelle ligne. printf '1\n2' | tac -rs . | od -vAn -tcsorties \n 2 1au lieu de 2 \n 1. Vous auriez également besoin LC_ALL=Cou .pourriez faire correspondre des caractères multi-octets.
Stéphane Chazelas
4
LC_ALL=C tac -rs $'.\\|\n'semble fonctionner cependant.
Stéphane Chazelas