¿Cómo revertir el contenido del archivo binario?

11

Estaba resolviendo un desafío donde encontré un archivo de datos sin extensión de archivo. El filecomando muestra que es un data file (application/octet-stream). El hdcomando muestra GNP. en la ultima linea. Entonces, si revierto este archivo, obtendré el archivo de formato .PNG , busqué en todas partes pero no encontré una solución que explicara cómo revertir el contenido de un archivo binario.

Prvt_Yadav
fuente

Respuestas:

11

Con xxd(desde vim) y tac(desde GNU coreutils, también tail -ren algunos sistemas):

< file.gnp xxd -p -c1 | tac | xxd -p -r > file.png
Stéphane Chazelas
fuente
¿Hay alguna forma de que esto se combine con vi.stackexchange.com/a/2237/10649 ?
Probé
Esto no es una solución porque reflejará todo el archivo.
Philippe Delteil
@PhilippeDelteil, ¿ reflejar todo el archivo era lo que el OP está pidiendo aquí? ¿Qué más quieres que haga?
Stéphane Chazelas
4

En zsh(el único shell que puede manejar internamente datos binarios (a menos que desee considerar el enfoque de codificación base64 de ksh93 )):

zmodload zsh/mapfile
(LC_ALL=C; printf %s ${(s::Oa)mapfile[file.gnp]} > file.png)
  • LC_ALL=C: los caracteres son bytes
  • $mapfile[file.gnp]: contenido del file.gnparchivo
  • s::: divide la cadena en sus componentes de bytes
  • Oa: Order inverso en asubíndice de matriz que matriz
Stéphane Chazelas
fuente
1
zshNo es el único shell que puede manejar datos binarios.
fpmurphy
2

Aquí hay una forma de invertir un archivo binario usando ksh93. He dejado el código "suelto" para que sea más fácil de entender.

#!/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
fuente
bonito. Esa es la única respuesta hasta ahora que no implica almacenar todo el archivo en la memoria. Sin embargo, es terriblemente ineficiente ya que realiza varias llamadas al sistema para cada byte del archivo (y conversiones a / desde base64), por lo que tampoco sería adecuado para archivos que no caben en la memoria. En mi máquina, procesa archivos a aproximadamente 10 KB / s
Stéphane Chazelas
Tenga en cuenta que el primero readanterior no debería leer nada, ya que se hace al final del archivo.
Stéphane Chazelas
Tratando de entender por qué era tan lento, intenté ejecutarlo stracey ksh93parece que se comporta de manera muy extraña, donde busca en todo el lugar dentro del archivo y lee grandes cantidades en ese momento. Quizás una variante de github.com/att/ast/issues/15
Stéphane Chazelas
@ StéphaneChazelas. No hay misterio en cuanto a por qué es relativamente lento. Dentro del ciclo tiene que buscar hacia atrás cada vez que lee un byte. Esto puede reducirse significativamente en un factor de 20 o incluso más leyendo y escribiendo más de un byte a la vez. El lado de escritura de las cosas también se puede optimizar. Hay muchas otras técnicas disponibles para acelerar aún más las cosas. Dejaré ese ejercicio a usted.
fpmurphy
Prueba straceel guión para ver a qué me refiero. ksh93lee los archivos miles de veces. Por ejemplo, antes de leer el primer byte, busca 64KiB al final del archivo, lee 64KiB, luego busca antes del último byte y lee 1 byte y hace algo similar para cada byte. Tenga en cuenta que lo que puede hacer con esas cadenas codificadas en base64 es limitado, por lo que si lee más de un byte a la vez, será más difícil extraer los bytes individuales de eso.
Stéphane Chazelas
2

Con perl:

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

Prueba de rendimiento:

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

Resultado:

  • Probado localmente: mi solución es la más rápida, perl -0777 -Fla más lenta.
  • Probado en ¡ Pruébelo en línea! : mi solución es la más rápida, xxdes la más lenta.

Nota: el tiempo de diffejecución debe ser el mismo para todas las soluciones, ya que la salida debe ser la misma.

usuario202729
fuente
1
He eliminado mi perluno. En ese momento no me había dado cuenta de que también reversepodía invertir las cadenas, por lo que hacer esa división no tenía mucho sentido y su versión es mucho mejor.
Stéphane Chazelas
1

Intenté lo siguiente:

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

La idea es forzar 'tac' usando cualquier carácter como separador. Lo intenté en un archivo binario y parecía funcionar, pero cualquier confirmación sería apreciada.

La principal ventaja es que no carga el archivo en la memoria.

Bouteille
fuente
No funciona para mí (aquí con GNU tac8.28) cuando la entrada contiene caracteres de nueva línea. printf '1\n2' | tac -rs . | od -vAn -tcsalidas en \n 2 1lugar de 2 \n 1. También necesitaría LC_ALL=Co .podría coincidir con caracteres de varios bytes.
Stéphane Chazelas
44
LC_ALL=C tac -rs $'.\\|\n'parece funcionar sin embargo.
Stéphane Chazelas