¿Cómo encontrar archivos con caracteres 100% NUL en sus contenidos?

16

¿Cuál es el comando de línea de comandos de Linux que puede identificar dichos archivos?

AFAIK el findcomando (o grep) solo puede coincidir con una cadena específica dentro del archivo de texto. Pero quiero hacer coincidir todo el contenido, es decir, quiero ver qué archivos coinciden con la expresión regular \0+, ignorando los caracteres de final de línea . Tal vez el find . cat | grepidioma podría funcionar, pero no sé cómo hacer grep ignorando líneas (y tratar el archivo como binario).

Antecedentes: cada pocos días, cuando mi computadora portátil se congela, mi partición btrfs pierde información: los archivos abiertos para escritura reemplazan sus contenidos con ceros (el tamaño del archivo permanece más o menos intacto). Uso la sincronización y no quiero que se propaguen estos archivos falsos: necesito una forma de identificarlos para poder tomarlos de la copia de seguridad.

Adam Ryczkowski
fuente
¿Te refieres a los archivos que tienen ceros numéricos?
Rahul Patil
2
Creo que se trata de caracteres NULL en lugar de ceros numéricos.
gertvdijk
10
Retrocedamos aquí. ¿Cada pocos días, cuando su computadora portátil se congela? ¿Por qué no estamos tratando de arreglar eso , el verdadero problema aquí?
D_Bye
2
@D_Bye, esa es una buena idea, pero hasta ahora no llegó demasiado lejos: [ unix.stackexchange.com/questions/57894/…
Adam Ryczkowski
1
¿ha considerado la -vopción de grep: filtrar todos los archivos que tienen cualquier byte de 1 a 255.
ctrl-alt-delor

Respuestas:

10

Puedes grepusar ␀ caracteres usando el modo Perl regex:

$ echo -ne "\0\0" > nul.bin
$ echo -ne "\0x\0" > non-nul.bin
$ grep -P "[^\0]" *.bin
Binary file non-nul.bin matches

Entonces puedes usar esto:

for path in *.foo
do
    grep -P "[^\0]" "$path" || echo "$path"
done
l0b0
fuente
Obtengo resultados inesperados, usando GNU grep 2.5.4. Independientemente de si uso --binary-files=texto --binary-files=binary, da un trueresultado para todos los valores de datos no vacíos, por ejemplo. "\0\0", "\0x\0", "abcd"... El código exacto que utiliza es: for typ in binary text ;do for dat in '\0\0' '\0x\0' 'abcd' '' ;do printf "$dat" >f; grep --binary-files=$typ -P '[^\0]' f >/dev/null && echo true || echo false; done; done
Peter.O
1
Ahora lo he intentado más GNU grep) 2.10. Esta última versión da los resultados esperados ... entonces, un tardío +1
Peter.O
1
Falla en un archivo creado con printf '\0\n\0\0\n\n' > fileo printf '\n' > filepara eso importa.
Stéphane Chazelas 01 de
2
@ StéphaneChazelas OP dijo "ignorando los caracteres finales de línea". Por lo que cualquier archivo que consta de sólo \0y \npersonajes (incluso cero de cualquiera) sería un partido.
l0b0
6

Estoy de acuerdo con lo que D_Bye dice acerca de encontrar la raíz del problema.

De todos modos, para verificar si un archivo solo contiene \0y / o \npodría usar tr:

<file tr -d '\0\n' | wc -c

Que devuelve 0 para nulo / nueva línea y archivos vacíos.

Thor
fuente
2
tr -d '\0\n'resuelve el problema de la nueva línea, que luego solo deja el problema (?) de los archivos vacíos que se enumeran en la salida ... Sin embargo, procesa cada byte de cada archivo (lo que puede o no ser un problema) +1
Peter.O
@ Peter.O: Perdí el requisito de nueva línea, gracias. Esta solución no está muy optimizada y si se ejecuta en una gran cantidad de datos, sería mejor con una solución que avanza al encontrar bytes que no coinciden.
Thor
Funciona muy bien. En mi caso, solo tuve que asegurarme de excluir los archivos de longitud cero. Gracias.
Adam Ryczkowski
1
Sin embargo, esto también contará los archivos con nuevas líneas como "vacíos".
Chris Down
1
@ChrisDown: Dejé el texto de respuesta claro en cuanto a lo que hace. No está claro qué quiere hacer el OP con los archivos de nueva línea.
Thor
5

Sospecho que esos archivos son escasos, es decir, no tienen espacio en disco asignado, solo especifican un tamaño de archivo ( duinformarían 0 para ellos).

En ese caso, con GNU find, podría hacer (suponiendo que ninguna ruta de archivo contenga caracteres de nueva línea):

find . -type f -size +0 -printf '%b:%p\n' | grep '^0:' | cut -d: -f2-
Stéphane Chazelas
fuente
Buen punto. Nunca pensé en ello. Lo intentaré. El uso duevitará rayar el contenido de cada archivo en el sistema de archivos, por lo que todo el procedimiento no tomaría más de 30 minutos en completarse.
Adam Ryczkowski
(y más printf %barriba informa qué duinformaría)
Stéphane Chazelas
Cambiaría -size +0a -size +1para que los archivos de longitud cero se excluyan de los resultados. También los archivos que contienen \nen su ruta causarán problemas para este comando.
Tyson
@Tyson -size +0es para tamaños estrictamente mayores que 0. -size +1sería para tamaños estrictamente mayores que 512. La limitación de la nueva línea ya se mencionó.
Stéphane Chazelas
@ StéphaneChazelas Gracias por aclararme con respecto a que -size +1, de hecho, tienes razón. He arreglado mi respuesta. :-)
Tyson
4

Aquí hay un pequeño programa de Python que puede hacerlo:

import sys

def only_contains_nulls(fobj, chunk_size=1024):
    first = True
    while True:
        data = fobj.read(chunk_size)
        if not data:
            if first:
                return 1  # No data
            else:
                return 0
        if data.strip("\0"):
            return 1
        first = False

if __name__ == '__main__':
    with open(sys.argv[1]) as f:
        sys.exit(only_contains_nulls(f))

Y en acción:

$ printf '\0\0\0' > file
$ ./onlynulls file && echo "Only nulls" || echo "Non-null characters"
Only nulls
$ printf a >> file
$ ./onlynulls file && echo "Only nulls" || echo "Non-null characters"
Non-null characters

Puede comprobar varios archivos mediante el uso de Find -exec, xargs, GNU parallel, y programas similares. Alternativamente, esto imprimirá los nombres de archivo que deben tratarse:

files=( file1 file2 )
for file in "${files[@]}"; do
    ./onlynulls "$file" || printf '%s\n' "$file"
done

Tenga en cuenta que si va a pasar la salida de este a otro programa, los nombres de archivo pueden contener nuevas líneas, por lo que debe delimitarlo de manera diferente (adecuadamente, con \0).

Si tiene muchos archivos, sería mejor usar una opción para el procesamiento en paralelo, ya que esto solo lee un archivo a la vez.

Chris Down
fuente
2
Tenga cuidado, los archivos de longitud cero (por ejemplo: /etc/nologin, ~/.hushlogin, .nomedia, ...) están mal identificados por esta respuesta.
Tyson
@ Tyson ¡Gracias por señalarlo! Lo acabo de arreglar.
Chris Down
3

Busque archivos que contengan solo caracteres nulos '\ 0' y caracteres de nueva línea '\ n'.
El qin sed hace que cada búsqueda de archivo se cierre inmediatamente al encontrar cualquier carácter no nulo en una línea.

find -type f -name 'file-*' |
  while IFS= read -r file ;do 
      out=$(sed -n '1=; /^\x00\+$/d; i non-null
                      ; q' "$file")
      [[ $out == "1" ]] &&  echo "$file"
  done

Hacer archivos de prueba

> file-empty
printf '%s\n' 'line1' 'line2' 'line3'      > file-with-text           
printf '%4s\n' '' '' xx | sed 's/ /\x00/g' > file-with-text-and-nulls
printf '%4s\n' '' '' '' | sed 's/ /\x00/g' > file-with-nulls-and-newlines
printf '%4s'   '' '' '' | sed 's/ /\x00/g' > file-with-nulls-only

salida

./file-with-nulls-and-newlines
./file-with-nulls-only
Peter.O
fuente
O -print0parece que falta el argumento findo la IFS=parte está en mal estado. ¿Cuál fue el delimitador previsto?
Tyson
3

Esta sola línea es la forma más eficaz de encontrar el 100% nul archivos usando GNU find, xargsy grep(suponiendo que este último está construido con el apoyo PCRE):

find . -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -r0 grep -LP "[^\x00]" --

Las ventajas de este método sobre otras respuestas proporcionadas son:

  • los archivos no dispersos se incluyen en la búsqueda.
  • los archivos no legibles no se pasan a grep, evitando Permission deniedadvertencias.
  • grepdejará de leer datos de los archivos después de encontrar cualquier byte que no sea nulo ( LC_ALL=Cse utiliza para asegurarse de que cada byte se interprete como un carácter ).
  • Los archivos vacíos (cero bytes) no se incluyen en los resultados.
  • Menos grepprocesos verifican eficientemente múltiples archivos.
  • las rutas que contienen líneas nuevas o que comienzan con -se manejan correctamente.
  • funciona en la mayoría de los sistemas integrados que carecen de Python / Perl.

Pasar la -Zopción ay grepusarla xargs -r0 ...permite realizar más acciones en los archivos 100% nul (por ejemplo: limpieza):

find . -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -0 grep -ZLP "[^\x00]" -- |
  xargs -r0 rm --

También recomiendo usar las findopciones -Ppara evitar seguir enlaces simbólicos y -xdevpara evitar el desplazamiento de sistemas de archivos (por ejemplo: montajes remotos, árboles de dispositivos, montajes de enlace, etc.).

Para ignorar los caracteres finales de línea , la siguiente variante debería funcionar (aunque no creo que sea una buena idea):

find . -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -r0 grep -LP "[^\x00\r\n]" --

Poniendo todo junto, incluida la eliminación de los archivos no deseados (100% nul / caracteres de nueva línea) para evitar que se realicen copias de seguridad:

find -P . -xdev -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -0 grep -ZLP "[^\x00\r\n]" -- |
  xargs -0 rm --

No recomiendo incluir archivos vacíos (cero bytes), a menudo existen para propósitos muy específicos .

Tyson
fuente
Ser el más rápido de tantas alternativas es una afirmación audaz. Marcaré su respuesta como aceptada si agrega un punto de referencia :-)
Adam Ryczkowski
Tal punto de referencia dependería de muchos factores, incluido el rendimiento de los diversos subsistemas de disco.
Tyson
Por supuesto, pero cualquier cosa es mejor que nada. Varios enfoques optimizan el uso de la CPU de manera diferente, por lo que tiene sentido compararlo en SSD o incluso en archivos en caché. Tome la máquina en la que trabaja actualmente, escriba una oración de lo que es (tipo de CPU, no de núcleos, RAM, tipo de disco duro), describa el conjunto de archivos (por ejemplo, clon de fuente del núcleo + archivo de 1GB lleno \0con un agujero de 900MB) y momento actual de los resultados. Si lo hace de una manera que el punto de referencia es convincente para usted, lo más probable es que sea convincente para todos nosotros
Adam Ryczkowski
La "mayoría de los sistemas integrados" no tienen utilidades GNU. Más probablemente los de busybox.
Stéphane Chazelas
-Pes el valor por defecto en find. Si quieres seguir enlaces simbólicos, es -L/ -follow. Encontrará que POSIX ni siquiera especifica esa opción find(aunque POSIX fue quien introdujo esos -P / -H / -L para algunos comandos).
Stéphane Chazelas
0

Para usar GNU sed puede usar la -zopción, que define una línea como cadenas terminadas en cero y hacer coincidir y eliminar líneas vacías de esta manera:

if [ "$( sed -z '/^$/d' "$file" | head -c 1 | wc -c )" -eq 0 ]; then
    echo "$file contains only NULL!"
fi

El comando head en el medio es solo una optimización.

mxmlnkn
fuente
-1

Pitón

Archivo único

Defina el alias:

alias is_binary="python -c 'import sys; sys.exit(not b\"\x00\" in open(sys.argv[1], \"rb\").read())'"

Pruébalo:

$ is_binary /etc/hosts; echo $?
1
$ is_binary `which which`; echo $?
0

Archivos múltiples

Encuentra todos los archivos binarios de forma recursiva:

IS_BINARY='import sys; sys.exit(not b"\x00" in open(sys.argv[1], "rb").read())'
find . -type f -exec bash -c "python -c '$IS_BINARY' {} && echo {}" \;

Para buscar todos los archivos no binarios, cambie &&con ||.

kenorb
fuente
1
La pregunta para identificar los archivos que contienen solamente (haciendo caso omiso de los saltos de línea) caracteres nul, el código Python que aquí se identifica los archivos que contienen los caracteres nul.
Tyson