Linux: ¿calcular un solo hash para una carpeta y contenido determinados?


98

¡Seguramente debe haber una manera de hacer esto fácilmente!

Probé las aplicaciones de línea de comandos de Linux como sha1sumy, md5sumpero parece que solo pueden calcular valores hash de archivos individuales y generar una lista de valores hash, uno para cada archivo.

Necesito generar un solo hash para todo el contenido de una carpeta (no solo los nombres de archivo).

Me gustaria hacer algo como

sha1sum /folder/of/stuff > singlehashvalue

Editar: para aclarar, mis archivos están en varios niveles en un árbol de directorios, no todos están en la misma carpeta raíz.


1
Por 'contenido completo', ¿te refieres a los datos lógicos de todos los archivos en el directorio o sus datos junto con el meta mientras se llega al hash raíz? Dado que los criterios de selección de su caso de uso son bastante amplios, he tratado de abordar algunos prácticos en mi respuesta.
six-k

Respuestas:


124

Una forma posible sería:

ruta sha1sum / a / carpeta / * | sha1sum

Si hay un árbol de directorios completo, probablemente sea mejor usar find y xargs. Un posible comando sería

buscar ruta / a / carpeta -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum

Y, finalmente, si también necesitas tener en cuenta los permisos y directorios vacíos:

(find path/to/folder -type f -print0  | sort -z | xargs -0 sha1sum;
 find path/to/folder \( -type f -o -type d \) -print0 | sort -z | \
   xargs -0 stat -c '%n %a') \
| sha1sum

Los argumentos de statharán que imprima el nombre del archivo, seguido de sus permisos octales. Los dos hallazgos se ejecutarán uno tras otro, lo que generará el doble de la cantidad de E / S del disco, el primero buscará todos los nombres de archivos y sumará el contenido, el segundo buscará todos los nombres de archivos y directorios, el nombre de impresión y el modo. La lista de "nombres de archivos y sumas de verificación", seguida de "nombres y directorios, con permisos", se agregará a la suma de verificación para obtener una suma de verificación más pequeña.


2
y no olvide establecer LC_ALL = POSIX, para que las diversas herramientas creen una salida independiente de la configuración regional.
David Schmitt

2
Encontré gato | sha1sum sea considerablemente más rápido que sha1sum | sha1sum. YMMV, pruebe cada uno de estos en su sistema: tiempo de búsqueda de ruta / a / carpeta -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum; tiempo buscar ruta / a / carpeta -type f -print0 | sort -z | xargs -0 cat | sha1sum
Bruno Bronosky

5
@RichardBronosky - Supongamos que tenemos dos archivos, A y B. A contiene "foo" y B contiene "bar was here". Con su método, no podríamos separar eso de dos archivos C y D, donde C contiene "foobar" y D contiene "estaba aquí". Al aplicar hash a cada archivo individualmente y luego a todos los pares de "hash de nombre de archivo", podemos ver la diferencia.
Vatine

2
Para que esto funcione independientemente de la ruta del directorio (es decir, cuando desee comparar los hash de dos carpetas diferentes), debe usar una ruta relativa y cambiar al directorio apropiado, porque las rutas se incluyen en el hash final:find ./folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum
robbles

3
@robbles Eso es correcto y por qué no puse una inicial /en el path/to/folderbit.
Vatine

26
  • Utilice una herramienta de detección de intrusiones en el sistema de archivos como asistente .

  • hash una bola de alquitrán del directorio:

    tar cvf - /path/to/folder | sha1sum

  • Codifique algo usted mismo, como el delineador de vatine :

    find /path/to/folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum


3
+1 para la solución de alquitrán. Ese es el más rápido, pero eliminar la v. Verbosidad solo lo ralentiza.
Bruno Bronosky

7
tenga en cuenta que la solución tar asume que los archivos están en el mismo orden cuando los compara. Si lo son dependerá del sistema de archivos en el que residan los archivos al realizar la comparación.
nos

5
El hash de git no es adecuado para este propósito ya que el contenido del archivo es solo una parte de su entrada. Incluso para la confirmación inicial de una rama, el hash se ve afectado por el mensaje de confirmación y los metadatos de confirmación también, como el momento de la confirmación. Si confirma la misma estructura de directorio varias veces, obtendrá un hash diferente cada vez, por lo que el hash resultante no es adecuado para determinar si dos directorios son copias exactas entre sí enviando solo el hash.
Zoltan

1
@Zoltan, el hash de git está perfectamente bien, si usa un hash de árbol y no un hash de confirmación.
Hobbs

1
@hobbs La respuesta originalmente decía "cometer hash", que ciertamente no es adecuado para este propósito. El hash del árbol suena como un candidato mucho mejor, pero aún podría haber trampas ocultas. Una que me viene a la mente es que tener el bit ejecutable configurado en algunos archivos cambia el hash del árbol. Tienes que emitir git config --local core.fileMode falseantes de comprometerte para evitar esto. No sé si hay más advertencias como esta.
Zoltan

14

Tu puedes hacer tar -c /path/to/folder | sha1sum


17
Si desea replicar esa suma de comprobación en una máquina diferente, es posible que tar no sea una buena opción, ya que el formato parece tener espacio para la ambigüedad y existe en muchas versiones, por lo que tar en otra máquina puede producir una salida diferente de los mismos archivos.
perro lento

2
preocupaciones válidas de slowdog no obstante, si se preocupan por el contenido del archivo, permisos, etc., pero no fecha de modificación, puede agregar la --mtimeopción de este modo: tar -c /path/to/folder --mtime="1970-01-01" | sha1sum.
Archivo binario

@ S.Lott si el tamaño del directorio es grande, quiero decir si el tamaño del directorio es tan grande, comprimirlo y obtener md5 llevará más tiempo
Kasun Siyambalapitiya

13

Si solo desea verificar si algo en la carpeta cambió, le recomiendo este:

ls -alR --full-time /folder/of/stuff | sha1sum

Solo le dará un hash de la salida de ls, que contiene carpetas, subcarpetas, sus archivos, su marca de tiempo, tamaño y permisos. Prácticamente todo lo que necesitaría para determinar si algo ha cambiado.

Tenga en cuenta que este comando no generará hash para cada archivo, pero es por eso que debería ser más rápido que usar find.


1
No estoy seguro de por qué esto no tiene más votos positivos dada la simplicidad de la solución. ¿Alguien puede explicar por qué esto no funcionaría bien?
Dave C

1
Supongo que esto no es ideal, ya que el hash generado se basará en el propietario del archivo, la configuración del formato de fecha, etc.
Ryota

1
El comando ls se puede personalizar para generar lo que desee. Puede reemplazar -l con -gG para omitir el grupo y el propietario. Y puede cambiar el formato de fecha con la opción --time-style. Básicamente, consulte la página de manual de ls y vea qué se adapta a sus necesidades.
Shumoapp

@DaveC Porque es bastante inútil. Si desea comparar nombres de archivos, simplemente compárelos directamente. No son tan grandes.
Navin

7
@Navin A partir de la pregunta, no queda claro si es necesario aplicar un hash al contenido del archivo o detectar un cambio en un árbol. Cada caso tiene sus usos. Almacenar nombres de archivos de 45K en un árbol del kernel, por ejemplo, es menos práctico que un solo hash. ls -lAgGR --block-size = 1 --time-style = +% s | sha1sum funciona muy bien para mí
yashma

5

Un enfoque sólido y limpio

  • Primero lo primero, ¡no acapares la memoria disponible ! Hash un archivo en trozos en lugar de alimentar el archivo completo.
  • Diferentes enfoques para diferentes necesidades / propósitos (todos los siguientes o elija lo que corresponda):
    • Hash solo el nombre de entrada de todas las entradas en el árbol del directorio
    • Hash el contenido del archivo de todas las entradas (dejando el meta como, el número de inodo, ctime, atime, mtime, size, etc., ya tienes la idea)
    • Para un enlace simbólico, su contenido es el nombre de referencia. Aplícalo o elige saltear
    • Siga o no siga (nombre resuelto) el enlace simbólico mientras realiza el hash del contenido de la entrada
    • Si es un directorio, su contenido son solo entradas de directorio. Mientras se atraviesa de forma recursiva, eventualmente se les aplicará un hash, pero ¿deberían los nombres de entrada de directorio de ese nivel tener hash para etiquetar este directorio? Útil en casos de uso donde se requiere el hash para identificar un cambio rápidamente sin tener que atravesar profundamente para hash el contenido. Un ejemplo sería el cambio de nombre de un archivo, pero el resto del contenido permanece igual y todos son archivos bastante grandes.
    • Maneja bien archivos grandes (de nuevo, cuidado con la RAM)
    • Manejar árboles de directorios muy profundos (tenga en cuenta los descriptores de archivos abiertos)
    • Manejar nombres de archivos no estándar
    • ¿Cómo proceder con archivos que son sockets, tuberías / FIFO, dispositivos de bloque, dispositivos de caracteres? ¿Debes picarlos también?
    • No actualice el tiempo de acceso de ninguna entrada mientras atraviesa porque esto será un efecto secundario y contraproducente (¿intuitivo?) Para ciertos casos de uso.

Esto es lo que tengo encima de mi cabeza, cualquiera que haya pasado algún tiempo trabajando en esto prácticamente habría captado otras trampas y casos de esquina.

Aquí hay una herramienta , muy liviana en memoria, que se ocupa de la mayoría de los casos, puede ser un poco tosca en los bordes, pero ha sido bastante útil.

Un ejemplo de uso y salida de dtreetrawl.

Usage:
  dtreetrawl [OPTION...] "/trawl/me" [path2,...]

Help Options:
  -h, --help                Show help options

Application Options:
  -t, --terse               Produce a terse output; parsable.
  -j, --json                Output as JSON
  -d, --delim=:             Character or string delimiter/separator for terse output(default ':')
  -l, --max-level=N         Do not traverse tree beyond N level(s)
  --hash                    Enable hashing(default is MD5).
  -c, --checksum=md5        Valid hashing algorithms: md5, sha1, sha256, sha512.
  -R, --only-root-hash      Output only the root hash. Blank line if --hash is not set
  -N, --no-name-hash        Exclude path name while calculating the root checksum
  -F, --no-content-hash     Do not hash the contents of the file
  -s, --hash-symlink        Include symbolic links' referent name while calculating the root checksum
  -e, --hash-dirent         Include hash of directory entries while calculating root checksum

Un fragmento de salida amigable para los humanos:

...
... //clipped
...
/home/lab/linux-4.14-rc8/CREDITS
        Base name                    : CREDITS
        Level                        : 1
        Type                         : regular file
        Referent name                :
        File size                    : 98443 bytes
        I-node number                : 290850
        No. directory entries        : 0
        Permission (octal)           : 0644
        Link count                   : 1
        Ownership                    : UID=0, GID=0
        Preferred I/O block size     : 4096 bytes
        Blocks allocated             : 200
        Last status change           : Tue, 21 Nov 17 21:28:18 +0530
        Last file access             : Thu, 28 Dec 17 00:53:27 +0530
        Last file modification       : Tue, 21 Nov 17 21:28:18 +0530
        Hash                         : 9f0312d130016d103aa5fc9d16a2437e

Stats for /home/lab/linux-4.14-rc8:
        Elapsed time     : 1.305767 s
        Start time       : Sun, 07 Jan 18 03:42:39 +0530
        Root hash        : 434e93111ad6f9335bb4954bc8f4eca4
        Hash type        : md5
        Depth            : 8
        Total,
                size           : 66850916 bytes
                entries        : 12484
                directories    : 763
                regular files  : 11715
                symlinks       : 6
                block devices  : 0
                char devices   : 0
                sockets        : 0
                FIFOs/pipes    : 0

1
¿Puede dar un breve ejemplo para obtener un sha256 sólido y limpio de una carpeta, tal vez para una carpeta de Windows con tres subdirectorios y algunos archivos en cada uno?
Ferit

3

Si solo desea aplicar un hash al contenido de los archivos, ignorando los nombres de archivo, puede usar

cat $FILES | md5sum

Asegúrese de tener los archivos en el mismo orden al calcular el hash:

cat $(echo $FILES | sort) | md5sum

Pero no puede tener directorios en su lista de archivos.


2
Mover el final de un archivo al principio del archivo que lo sigue alfabéticamente no afectaría el hash, pero debería hacerlo. Debería incluirse un delimitador de archivo o longitudes de archivo en el hash.
Jason Stangroome

3

Otra herramienta para lograrlo:

http://md5deep.sourceforge.net/

Como suena: como md5sum pero también recursivo, además de otras características.


1
Si bien este enlace puede responder a la pregunta, es mejor incluir las partes esenciales de la respuesta aquí y proporcionar el enlace como referencia. Las respuestas de solo enlace pueden dejar de ser válidas si cambia la página enlazada.
Mamoun Benghezal

3

Si se trata de un repositorio de git y desea ignorar cualquier archivo en .gitignore, es posible que desee utilizar esto:

git ls-files <your_directory> | xargs sha256sum | cut -d" " -f1 | sha256sum | cut -d" " -f1

Esto me está funcionando bien.


¡Muchas gracias! :)
visortelle

Para muchas aplicaciones, este enfoque es superior. El hash solo de los archivos de código fuente obtiene un hash suficientemente único en mucho menos tiempo.
John McGehee


2

Tuve que registrarme en un directorio completo para ver los cambios de archivo.

Pero excluyendo, marcas de tiempo, propiedad de directorios.

El objetivo es obtener una suma idéntica en cualquier lugar, si los archivos son idénticos.

Incluyendo alojado en otras máquinas, sin importar nada menos los archivos, o un cambio en ellos.

md5sum * | md5sum | cut -d' ' -f1

Genera una lista de hash por archivo, luego concatena esos hash en uno.

Esto es mucho más rápido que el método tar.

Para una mayor privacidad en nuestros hashes, podemos usar sha512sum en la misma receta.

sha512sum * | sha512sum | cut -d' ' -f1

Los hash también son idénticos en cualquier lugar con sha512sum, pero no se conoce una forma de revertirlo.


Esto parece mucho más simple que la respuesta aceptada para hacer hash en un directorio. No encontraba confiable la respuesta aceptada. Un problema ... ¿Existe la posibilidad de que los hashes aparezcan en un orden diferente? sha256sum /tmp/thd-agent/* | sortes lo que estoy tratando de hacer para un pedido confiable, y luego simplemente lo hago.
thinktt

Hola, parece que los hashes vienen en orden alfabético por defecto. ¿Qué quieres decir con pedidos fiables? Tienes que organizar todo eso tú mismo. Por ejemplo, utilizando matrices asociativas, entrada + hash. Luego, clasifica esta matriz por entrada, esto le da una lista de hashes calculados en el orden de clasificación. Creo que puede usar un objeto json de lo contrario y hash todo el objeto directamente.
NVRM

Si entiendo, está diciendo que aplica el hash de los archivos en orden alfabético. Eso parece correcto. Algo en la respuesta aceptada anterior me estaba dando órdenes diferentes intermitentes a veces, así que solo estoy tratando de asegurarme de que eso no vuelva a suceder. Me quedaré con poner orden al final. Parece estar funcionando. El único problema con este método frente a la respuesta aceptada que veo es que no se ocupa de carpetas anidadas. En mi caso, no tengo carpetas, así que esto funciona muy bien.
thinktt

¿qué pasa ls -r | sha256sum?
NVRM

@NVRM lo probó y solo verificó los cambios en el nombre del archivo, no el contenido del archivo
Gi0rgi0s

1

Intenta hacerlo en dos pasos:

  1. crear un archivo con hashes para todos los archivos en una carpeta
  2. hash este archivo

Al igual que:

# for FILE in `find /folder/of/stuff -type f | sort`; do sha1sum $FILE >> hashes; done
# sha1sum hashes

O hazlo todo a la vez:

# cat `find /folder/of/stuff -type f | sort` | sha1sum

for F in 'find ...' ...no funciona cuando tienes espacios en los nombres (lo que siempre haces hoy en día).
mivk

1

Canalizaría los resultados para archivos individuales sort(para evitar una mera reordenación de archivos para cambiar el hash) en md5sumo sha1sum, lo que elija.


1

Escribí un script de Groovy para hacer esto:

import java.security.MessageDigest

public static String generateDigest(File file, String digest, int paddedLength){
    MessageDigest md = MessageDigest.getInstance(digest)
    md.reset()
    def files = []
    def directories = []

    if(file.isDirectory()){
        file.eachFileRecurse(){sf ->
            if(sf.isFile()){
                files.add(sf)
            }
            else{
                directories.add(file.toURI().relativize(sf.toURI()).toString())
            }
        }
    }
    else if(file.isFile()){
        files.add(file)
    }

    files.sort({a, b -> return a.getAbsolutePath() <=> b.getAbsolutePath()})
    directories.sort()

    files.each(){f ->
        println file.toURI().relativize(f.toURI()).toString()
        f.withInputStream(){is ->
            byte[] buffer = new byte[8192]
            int read = 0
            while((read = is.read(buffer)) > 0){
                md.update(buffer, 0, read)
            }
        }
    }

    directories.each(){d ->
        println d
        md.update(d.getBytes())
    }

    byte[] digestBytes = md.digest()
    BigInteger bigInt = new BigInteger(1, digestBytes)
    return bigInt.toString(16).padLeft(paddedLength, '0')
}

println "\n${generateDigest(new File(args[0]), 'SHA-256', 64)}"

Puede personalizar el uso para evitar imprimir cada archivo, cambiar el resumen del mensaje, eliminar el hash del directorio, etc. Lo he probado con los datos de prueba del NIST y funciona como se esperaba. http://www.nsrl.nist.gov/testdata/

gary-macbook:Scripts garypaduana$ groovy dirHash.groovy /Users/garypaduana/.config
.DS_Store
configstore/bower-github.yml
configstore/insight-bower.json
configstore/update-notifier-bower.json
filezilla/filezilla.xml
filezilla/layout.xml
filezilla/lockfile
filezilla/queue.sqlite3
filezilla/recentservers.xml
filezilla/sitemanager.xml
gtk-2.0/gtkfilechooser.ini
a/
configstore/
filezilla/
gtk-2.0/
lftp/
menus/
menus/applications-merged/

79de5e583734ca40ff651a3d9a54d106b52e94f1f8c2cd7133ca3bbddc0c6758

0

Podría sha1sumgenerar la lista de valores hash y luego sha1sumesa lista nuevamente, depende de qué es exactamente lo que desea lograr.


0

Aquí hay una variante simple y corta en Python 3 que funciona bien para archivos de pequeño tamaño (por ejemplo, un árbol de fuentes o algo, donde cada archivo individualmente puede caber en la RAM fácilmente), ignorando los directorios vacíos, según las ideas de las otras soluciones:

import os, hashlib

def hash_for_directory(path, hashfunc=hashlib.sha1):                                                                                            
    filenames = sorted(os.path.join(dp, fn) for dp, _, fns in os.walk(path) for fn in fns)         
    index = '\n'.join('{}={}'.format(os.path.relpath(fn, path), hashfunc(open(fn, 'rb').read()).hexdigest()) for fn in filenames)               
    return hashfunc(index.encode('utf-8')).hexdigest()                          

Funciona así:

  1. Encuentre todos los archivos en el directorio de forma recursiva y ordénelos por nombre
  2. Calcule el hash (predeterminado: SHA-1) de cada archivo (lee el archivo completo en la memoria)
  3. Haga un índice textual con líneas "filename = hash"
  4. Codifique ese índice de nuevo en una cadena de bytes UTF-8 y hash que

Puede pasar una función hash diferente como segundo parámetro si SHA-1 no es su taza de té.


0

Hasta ahora, la forma más rápida de hacerlo sigue siendo con alquitrán. Y con varios parámetros adicionales también podemos deshacernos de la diferencia causada por los metadatos.

Para usar tar para hash en el directorio, es necesario asegurarse de ordenar la ruta durante tar, de lo contrario, siempre es diferente.

tar -C <root-dir> -cf - --sort=name <dir> | sha256sum

ignorar el tiempo

Si no le importa el tiempo de acceso o modificar el tiempo, también use algo como --mtime='UTC 2019-01-01' para asegurarse de que todas las marcas de tiempo sean iguales.

ignorar la propiedad

Por lo general, necesitamos agregar --group=0 --owner=0 --numeric-ownerpara unificar los metadatos del propietario.

ignorar algunos archivos

utilizar --exclude=PATTERN

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.