cambiar el nombre de todos los archivos en un directorio al hash md5 de su nombre de archivo (no contenido)

11

Soy muy nuevo en Linux / línea de comando y necesito cifrar los nombres de los archivos 10K + (nombres únicos) para que coincidan con el nombre cifrado MD5 en la base de datos mySQL.
He visto cómo puede cambiar el nombre de un directorio de archivos y cómo obtener el hash de un archivo ( ¿mdsum? ), Pero estoy atascado en cómo obtener el hash del nombre del archivo y luego cambiar el nombre del archivo al hash generado que retiene la extensión es decir

mynicepicture.jpg > fba8255e8e9ce687522455f3e1561e53.jpg 

Parece que debería ser un simple cambio de nombre o mvlínea, pero no puedo entenderlo.
Muchas gracias por sus ideas.

PD: He visto el uso de las funciones de Perl en algunos ejemplos cercanos a lo que estoy buscando, pero no tengo idea de dónde / cómo usarlos.

BradH
fuente
3
¿Está seguro de que desea tener un hash del nombre del archivo y no el contenido del archivo?
Anthon
12
Nota: El hash MD5 no es un dispositivo de cifrado. MD5 ni siquiera es un hash criptográfico. Un hash, cualquier hash, es una transformación unidireccional de un conjunto de datos a un número. No es reversible. El cifrado real es reversible, siempre (dada la clave utilizada para el cifrado).
Kusalananda
1
fba8255e8e9ce687522455f3e1561e53Para mynicepicturequé sirve el hash MD5 , ¿eso significa que la extensión debe eliminarse antes del hash?
Kusalananda
@dessert Quiero decir que no hay concesionario si md5sum <<<"file name"el file namearchivo existe o no, porque lo considera como una cadena, excepto alimentarlo con el nombre de los archivos existentes.
αғsнιη

Respuestas:

14

No dijiste qué shell quieres usar, así que solo estoy asumiendo que Bash : la respuesta necesita ajustes para funcionar con otros shells.

for i in *; do sum=$(echo -n "$i"|md5sum); echo -- "$i" "${sum%% *}.${i##*.}"; done

Versión de script:

for i in *; do
  sum=$(echo -n "$i" | md5sum)
  echo -- "$i" "${sum%% *}.${i##*.}"
done

Este forbucle simple toma cada archivo en el directorio actual, calcula la suma md5 de su nombre y lo genera. Use esto para verificar la funcionalidad, si desea comenzar a cambiar el nombre, reemplace el segundo echopor mv.

Explicaciones

  • echo -n "$i" | md5sum- calcule la suma md5 del nombre completo del archivo, incluida la extensión del archivo ( Piping ), para despojar el cambio de extensión echo -n "$i"a uno de los siguientes:

    ${i%%.*}
    sed 's/\..*//' <<< "$i"
    echo "$i" | sed 's/\..*//'
  • sum=$(…)- ejecutar y guardar la salida en $sum( Sustitución de comandos )

  • ${sum%% *}- muestra todo hasta el primer espacio ( sustitución de parámetros ), igual que uno de los siguientes:

    $(sed 's/ .*//' <<< "$sum")
    $(echo "$sum" | sed 's/ .*//')
  • ${i##*.} - muestra todo después del último punto (sustitución de parámetro), igual que uno de los siguientes:

    $(sed 's/.*\.//' <<< "$i")
    $(echo "$i" | sed 's/.*\.//')

Si necesita cambiar el nombre de los archivos de forma recursiva en diferentes carpetas, use findcon la -execopción.

postre
fuente
6
#!/bin/bash

md5name () {
    local base=${1##*/}
    local ext=${base##*.}
    local dir=${1%/*}

    printf '%s' "${base%.$ext}" | md5sum |
    awk -v dir="$dir" -v ext="$ext" '{ printf("%s/%s.%s\n", dir, $1, ext) }'
}

dir=$HOME  # where your files are

for pathname in "$dir"/*; do
    test -f "$pathname" || continue
    echo mv "$pathname" "$( md5name "$pathname" )"
done

Este bashscript usa la md5sumutilidad de GNU coreutils para calcular el hash MD5 a partir del nombre base (sin extensión) de cualquier nombre de ruta. La función auxiliar md5namerealiza el cálculo real y generará el nuevo nombre con la ruta completa y la extensión.

La md5namefunción utiliza awkpara ensamblar el nuevo nombre de las partes del nombre de ruta dado y el resultado de md5sum.

Ejemplos de la función en uso por sí misma:

$ md5name '/some/path/file name here.extension'
/some/path/c9e89fa443d16da4b96ea858881320c9.extension

... donde c9e89fa443d16da4b96ea858881320c9está el hash MD5 de la cadena file name here.

Elimine echoel archivo del script en la parte superior para cambiar el nombre de los archivos. Es posible que desee guardar la salida del script original en el archivo (con el echolugar) si en algún momento necesita restaurar los nombres de los archivos a sus originales.

Tenga en cuenta que ejecutar esto dos veces en un conjunto de archivos calculará el hash MD5 de los hashes MD5, y que el nombre de archivo original se volverá irrecuperable a menos que tome notas cuidadosas sobre qué archivos se llaman después de cada ejecución del script.

Kusalananda
fuente
Al igual que para su información, la awkporción podría reemplazarse con while read sum dummy ; do printf "%s/%s.%s\n' $dir $sum $ext ; done ;Necesitas dummycapturar el '-'.
Robert Benson
@RobertBenson El problema con eso es que los nombres de archivo que contienen espacios estarían en mal estado.
Kusalananda
Buena llamada. Los nombres de archivo con espacios son malvados. Me gusta awka mí mismo y me ha llevado un tiempo de uso de bashlos servicios públicos en lugar de system()enawk
Robert Benson
5

Con perl's rename:

find . -name '*.jpg' -type f -exec rename -n '
  BEGIN{use Digest::MD5 qw(md5_hex)}
  my ($dir, $name, $ext) = m{(.*)/(.*)\.(.*)}s;
  $_ = "$dir/" . md5_hex($name) . ".$ext"' {} +

(eliminar -ncuando sea feliz).

Stéphane Chazelas
fuente
¡Asombroso! Esto calcula la suma md5 del nombre del archivo sin la extensión, ¿qué tal el nombre completo del archivo? OP no dijo si lo necesita con o sin él.
postre
1
No lo dijo, pero el ejemplo que da es exactamente eso.
Robert Benson
2

Para un AWKenfoque:

find [Directory] -type f [various other find options] | 
     awk '{orig=$0; 
           match($0,/^.*\//,path); sub("^"path[0], "");
           match($0, /.[[^.]+$/,ext); sub(ext[0]"$", "");
           ("echo \"" $0 "\"|md5sum") | getline;
           com=sprintf("mv \"%s\" \"%s%s%s\"", orig, p[0], $1, ext[0]);
           print(com)
           }'

Los findcomandos modernos no requieren un directorio para la entrada ., por lo que el [Directorio] podría dejarse en blanco. El -type fúnico encuentra archivos, lo cual es útil ya md5sumque no le gustan los directorios y cambiar el nombre del directorio mientras se ejecuta no sería una buena idea. Úselo -iname patternsi solo desea usar algunos archivos, por ejemplo -iname \*.dat, si el caso es importante, úselo en -namelugar de -iname.

Las match(...); sub(...)piezas extraen porciones del nombre del archivo y las reemplazan en la cadena de entrada. Tenga en cuenta que "^"y "$"están [pre / ap] pendientes para evitar reemplazar una cadena que puede repetir la ruta / extensión.

Reemplace print(com)con system(com)para realizar el cambio de nombre.

Si desea utilizar el md5sumarchivo real como nombre, puede utilizar el hecho de que md5sumgenera la suma y el nombre de archivo de entrada para hacer algo como:

 find -type f -exec md5sum '{}' ';' | 
     while read sum file ; do 
       [echo] mv "$file" "`dirname $file`/$sum".extension ; 
     done

El while read sum filetomará 2 argumentos, los resultados del md5sumcomando, y asignar sumy filelas variables con ellos. Como sumno debería tener espacios, readdebería funcionar bien.

Obviamente, se [echo]debe eliminar cuando se está ejecutando, pero siempre es una buena idea probar cualquier cambio programado para probar la búsqueda antes de ejecutar.

Todo esto supone que estás corriendo bash. Además, esto se puede escribir como una línea larga:

find -iname \*.jpg -exec md5sum '{}' ';' | while read sum file ; do mv "$file" "`dirname $file`/$sum".jpg ; done
Robert Benson
fuente
1
Parece que esto picará el contenido de los archivos. El OP quería hash el nombre (sin extensión).
Kusalananda
Supongo que ayudaría si leyera completamente la pregunta.
Robert Benson
2

Este enfoque a menudo me gusta usar.

ls | sed "s|^\(.*\)\.\([^\.]*\)$|mv \1.\2 \\`echo \1 \| md5sum \| cut -d' ' -f 1\\`.\2|" | sh -

El comando "ls" produce una secuencia de líneas de texto. El comando "sed" transforma cada línea con reglas de coincidencia de patrones. El comando "sed" genera un comando "mv" que luego se canaliza a través de un shell "sh" para su ejecución. Los parámetros del comando "mv" son como "mv oldfilename newfilename", que cambia el nombre del archivo. Construyo el nuevo nombre de archivo con un comando sed que toma la parte anterior al último punto, y lo hace eco en la entrada del comando "md5sum", y luego toma solo el hash de su salida.

Recorriendo mi proceso, primero enumere los archivos ('head -n 3' para ver las primeras 3 líneas):

ls | head -n 3
    1000-26092016.xml
    1000-27092016.xml
    12312-28092016.xml

Luego piense en la transformación con sed (aún no canaliza ningún comando generado a través de un shell)

ls | sed "s|^\(.*\)\.\([^\.]*\)$|mv \1.\2 \1.\2|" | head -n 3
    mv 1000-26092016.xml 1000-26092016.xml
    mv 1000-27092016.xml 1000-27092016.xml
    mv 12312-28092016.xml 12312-28092016.xml

Hay tres patrones de coincidencia:

^\(.*\)      = match from start-of-line up to a dot
\.           = matches a single dot
\([^\.]*\)$  = match 0-or-more non-dot chars from end of line

Quiero usar sed para reemplazar un nombre de archivo de entrada con "mv filename NEWfilename", pero como estoy canalizando comandos a través de un shell, puedo generar comandos que obtienen el md5sum, como este

echo "1000-26092016" | md5sum
    55b18a6b0add4a318b0079e18512b4e8  -

para obtener solo el hash

echo "1000-26092016" | md5sum | cut -d' ' -f 1
    55b18a6b0add4a318b0079e18512b4e8

En un shell de Unix, podemos usar operadores de backtick (`some_command`) para ejecutar un subcomando, por ejemplo

echo "howdy date there"
    howdy date there
echo "howdy `date` there"
    howdy Fri Sep 15 18:39:00 IST 2017 there

Volviendo al comando mv, quiero que sed produzca "mv here there" con "there" reemplazado por un comando backtick para obtener el md5sum. La cadena dentro de la cadena de reemplazo de sed comienza así

ls | sed "s|^\(.*\)\.\([^\.]*\)$|mv \1.\2 `echo \1 | md5sum | cut -d' ' -f 1`.\2|" | head -n 3
    mv 1000-26092016.xml     b026324c6904b2a9cb4b88d6d61c81d1.xml
    mv 1000-27092016.xml     b026324c6904b2a9cb4b88d6d61c81d1.xml
    mv 12312-28092016.xml    b026324c6904b2a9cb4b88d6d61c81d1.xml

Pero claramente está haciendo el mismo hash para cada nombre de archivo, ya que el comando backticked-command se ejecuta antes de que sed vea la cadena. Para detener el shell que ejecuta el comando backtick para que sed genere los backticks, tenemos que anteponer barras diagonales (también al carácter de tubería), así que nuevamente:

ls | sed "s|^\(.*\)\.\([^\.]*\)$|mv \1.\2 \`echo \1 \| md5sum \| cut -d' ' -f 1\`.\2|" | head -n 3
    mv 1000-26092016.xml     `echo 1000-26092016 | md5sum | cut -d' ' -f 1`.xml
    mv 1000-27092016.xml     `echo 1000-27092016 | md5sum | cut -d' ' -f 1`.xml
    mv 12312-28092016.xml    `echo 12312-28092016 | md5sum | cut -d' ' -f 1`.xml

La salida también necesita nombres de archivo para ser citados en caso de espacios, por lo que

ls | sed "s|^\(.*\)\.\([^\.]*\)$|mv \"\1.\2\" \"\`echo \1 \| md5sum \| cut -d' ' -f 1\`.\2\"|" | grep trick
    mv "a trick€€ fíle nÁme.xml" "`echo a trick€€ fíle nÁme | md5sum | cut -d' ' -f 1`.xml"

Así que vamos a probar este, pasándolo a través de un shell:

ls | sed "s|^\(.*\)\.\([^\.]*\)$|mv \"\1.\2\" \"\`echo \1 \| md5sum \| cut -d' ' -f 1\`.\2\"|" | grep trick | sh -

Funcionó ? supongo:

echo "a trick€€ fíle nÁme" | md5sum
    629db9c3071928ba0746f18444713b65  -
ls 629db9c3071928ba0746f18444713b65*
    629db9c3071928ba0746f18444713b65.xml

Aquí hay un enfoque para la verificación cruzada; use la opción "ls" "-i" para generar el sistema de archivos unix i-node (que no cambia con "mv"):

ls -1i | sort -n > .before
ls | sed "s|^\(.*\)\.\([^\.]*\)$|mv \"\1.\2\" \"\`echo \1 \| md5sum \| cut -d' ' -f 1\`.\2\"|" | sh -
ls -1i | sort -n > .after
cut -d' ' -f 1 .before | while read I ; do echo "mv'd \"`grep ${I} .before`\" to \"`grep ${I} .after`\"" | sed "s| *$I *||g" ; done | head -n 3
    mv'd "1000-26092016.xml" to "55b18a6b0add4a318b0079e18512b4e8.xml"
    mv'd "1000-27092016.xml" to "b1baa80d99d5edf85c8aeb98185dd440.xml"
    mv'd "12312-28092016.xml" to "2b2d692bd047b64c99f7b9161349d430.xml"

O, usando el comando "pegar" (paquete 'coreutils')

paste .before .after | head -n 3
    36703389 1000-26092016.xml  36703389 55b18a6b0add4a318b0079e18512b4e8.xml
    36703390 1000-27092016.xml  36703390 b1baa80d99d5edf85c8aeb98185dd440.xml
    36703391 12312-28092016.xml 36703391 2b2d692bd047b64c99f7b9161349d430.xml
jmullee
fuente
0

Me gusta esa respuesta de una línea, pero se rompe porque analiza el nombre del archivo. También lo subí un poco con sha hashes.

find -iname "*.jpg" -exec sha1sum '{}' ';' | while read sum file ; do mv -v "$file" "`dirname '$file'`/$sum".jpg ; done

Creo que también extrae los archivos y los coloca en la base de donde se ingresó el comando.

Gracias.

GoofProg
fuente
1
Probablemente deberíamos referirnos a la respuesta de la cual basó la suya.
Jeff Schaller