Quiero contar cuántas veces ocurre una determinada secuencia de bytes dentro de un archivo que tengo. Por ejemplo, quiero averiguar cuántas veces \0xdeadbeef
aparece el número dentro de un archivo ejecutable. En este momento estoy haciendo eso usando grep:
#/usr/bin/fish
grep -c \Xef\Xbe\Xad\Xde my_executable_file
(Los bytes están escritos en orden inverso porque mi CPU es little-endian)
Sin embargo, tengo dos problemas con mi enfoque:
- Esas
\Xnn
secuencias de escape solo funcionan en el caparazón de los peces. - grep realmente está contando el número de líneas que contienen mi número mágico. Si el patrón ocurre dos veces en la misma línea, solo contará una vez.
¿Hay alguna forma de solucionar estos problemas? ¿Cómo puedo hacer que este liner se ejecute en Bash Shell y cuente con precisión el número de veces que el patrón ocurre dentro del archivo?
bash
grep
escape-characters
hugomg
fuente
fuente
grep -o
11221122
, ¿cómo debería devolverse en una entrada112211221122
? ¿1 o 2?Respuestas:
Esta es la solución de línea única solicitada (para shells recientes que tienen "sustitución de proceso"):
Si no hay una "sustitución de proceso"
<(…)
disponible, solo use grep como filtro:A continuación se muestra la descripción detallada de cada parte de la solución.
Valores de bytes de números hexadecimales:
Su primer problema es fácil de resolver:
Cambie el superior
X
a uno inferiorx
y use printf (para la mayoría de los shells):O usar:
Para aquellos shells que eligen no implementar la representación '\ x'.
Por supuesto, traducir hexadecimal a octal funcionará en (casi) cualquier shell:
Donde "$ sh" es cualquier shell (razonable). Pero es bastante difícil mantenerlo correctamente citado.
Archivos binarios.
La solución más sólida es transformar el archivo y la secuencia de bytes (ambos) en alguna codificación que no tenga problemas con valores de caracteres impares como (nueva línea)
0x0A
o (byte nulo)0x00
. Ambos son bastante difíciles de administrar correctamente con herramientas diseñadas y adaptadas para procesar "archivos de texto".Una transformación como base64 puede parecer válida, pero presenta el problema de que cada byte de entrada puede tener hasta tres representaciones de salida dependiendo de si es el primer, segundo o tercer byte de la posición mod 24 (bits).
Transformación hexadecimal.
Es por eso que la transformación más robusta debería ser una que comience en cada límite de byte, como la simple representación HEX.
Podemos obtener un archivo con la representación hexadecimal del archivo con cualquiera de estas herramientas:
La secuencia de bytes para buscar ya está en hexadecimal en este caso.
:
Pero también podría ser transformado. A continuación se muestra un ejemplo de ida y vuelta hex-bin-hex:
La cadena de búsqueda se puede establecer a partir de la representación binaria. Cualquiera de las tres opciones presentadas anteriormente od, hexdump o xxd son equivalentes. Solo asegúrese de incluir los espacios para asegurarse de que la coincidencia esté en los límites de bytes (no se permite el cambio de mordisco):
Si el archivo binario se ve así:
Luego, una simple búsqueda grep dará la lista de secuencias coincidentes:
¿Una línea?
Todo se puede realizar en una línea:
Por ejemplo, buscar
11221122
en el mismo archivo necesitará estos dos pasos:Para "ver" los partidos:
… 0a 3131323231313232313132323131323231313232313132323131323231313232 313132320a
Tamponamiento
Existe la preocupación de que grep almacenará en el búfer todo el archivo y, si el archivo es grande, creará una gran carga para la computadora. Para eso, podemos usar una solución sed no tamponada:
El primer sed no tiene búfer (
-u
) y se usa solo para inyectar dos nuevas líneas en la secuencia por cadena coincidente. El segundosed
solo imprimirá las líneas coincidentes (cortas). El wc -l contará las líneas coincidentes.Esto almacenará solo algunas líneas cortas. Las cadenas coincidentes en el segundo sed. Esto debería ser bastante bajo en recursos utilizados.
O, algo más complejo de entender, pero la misma idea en un sed:
fuente
grep
que terminará cargándolo completo en la memoria (aquí dos veces el tamaño del archivo original + 1 debido a la codificación hexadecimal), por lo que al final, termina siendo más sobrecarga que elpython
enfoque o elperl
que tiene-0777
. También necesita unagrep
implementación que admita líneas de longitud arbitraria (las que admiten-o
generalmente lo hacen). Buena respuesta de lo contrario.od -An -tx1 | tr -d '\n'
ohexdump -v -e '/1 " %02x"'
con una cadena de búsqueda que también contenga espacios, evite esto, pero no veo esa soluciónxxd
.sed -u
(donde esté disponible) es para unbuffer. Eso significa que leerá un byte a la vez en la entrada y emitirá su salida de inmediato sin almacenar en búfer. En cualquier caso, aún tendrá que cargar toda la línea en el espacio del patrón, por lo que no ayudará aquí.Con GNU
grep
's-P
bandera (perl-regexp)LC_ALL=C
es para evitar problemas en entornos locales de varios bytes donde, degrep
lo contrario, se trataría de interpretar secuencias de bytes como caracteres.-a
trata archivos binarios equivalentes a archivos de texto (en lugar del comportamiento normal, dondegrep
solo imprime si hay al menos una coincidencia o no)fuente
grep
que coincida?-a
opción, de lo contrario grep responderá conBinary file file.bin matches
cualquier archivo que grep detecte como binario.Que trata los archivos de entrada como binarios (no hay traducción para saltos de línea o codificaciones, consulte perlrun ) y luego recorre los archivos de entrada sin imprimir incrementando un contador para todas las coincidencias del hexadecimal dado (o cualquier forma, vea perlre ) .
fuente
-0ooo
).$/
, con una compensación ligeramente diferente (uso de memoria proporcional a la distancia máxima entre tales secuencias):perl -nE 'BEGIN { $/ = "\xef\xbe\xad\xde" } chomp; $c++ unless eof && length; END { say $c }'
Con GNU
awk
, puedes hacer:Si alguno de los bytes son operadores ERE, tendrían que escaparse (con
\\
). Como0x2e
cuál.
debería ser ingresado como\\.
o\\\x2e
. Aparte de eso, debería funcionar con valores de bytes arbitrarios, incluidos 0 y 0xa.Tenga en cuenta que no es tan simple como solo
NR-1
porque hay un par de casos especiales:RT==""
.También tenga en cuenta que en el peor de los casos (si el archivo no contiene el término de búsqueda), el archivo terminará cargándose completo en la memoria).
fuente
La traducción más directa que veo es:
Cuando he utilizado
$'\xef'
como la fiesta de ANSI-citando (originalmente unaksh93
función, ahora con el apoyo dezsh
,bash
,mksh
, FreeBSDsh
) versión de peces de\Xef
, y se utilizagrep -o ... | wc -l
para contar los casos.grep -o
saca cada partido en una línea separada. El-a
indicador hace que grep se comporte en archivos binarios de la misma manera que en archivos de texto.-F
es para cadenas fijas, por lo que no necesita escapar de los operadores de expresiones regulares.Como en su
fish
caso, no puede usar ese enfoque si la secuencia a buscar incluye los bytes 0 o 0xa (nueva línea en ASCII).fuente
printf '%b' $(printf '\\%o ' $((0xef)) $((0xbe)) $((0xad)) $((0xde))) > hugohex'
sería el método más portátil de "shell puro". Por supuesto:printf "efbeadde" | xxd -p -r > hugohex
parece el método más práctico.Puede usar el
bytes.count
método de Python para obtener el número total de subcadenas no superpuestas en una cadena de bytes.Este one-liner cargará todo el archivo en la memoria, por lo que no es el más eficiente, pero funciona y es más legible que Perl; D
fuente
239I$ 190I$ 173I$ 222I$ HXA ERfile$Y 0UC <:S^EQA$; %C$> QC=
(gd & r)mmap()
un archivo en Python ; eso reduciría el compromiso de memoria.fuente
Creo que puedes usar Perl, pruébalo:
El comando Reemplazar
s
da el número de reemplazos realizados, -0777 significa no tratar la nueva línea como un carácter especial,e
- ejecutar el comando,say
para imprimir lo que sigue y luego imprimir el nuevo carácter de línea,n
no había captado completamente, pero no funciona sin / desde docs:fuente