renomeie todos os arquivos em um diretório para o hash md5 de seu nome de arquivo (não conteúdo)

11

Eu sou muito novo no linux / linha de comando e preciso criptografar os nomes de arquivos de 10K + (nomes exclusivos) para que eles correspondam ao nome criptografado MD5 no banco de dados mySQL.
Eu vi como você pode renomear um diretório de arquivos e como obter o hash de um arquivo ( mdsum? ), Mas estou empolgado em como obter o hash do nome do arquivo e renomeá-lo para manter o hash gerado a extensão ou seja

mynicepicture.jpg > fba8255e8e9ce687522455f3e1561e53.jpg 

Parece que deveria ser uma simples renomeação ou mvlinha, mas não consigo entender.
Muito obrigado por suas idéias

PS: Eu já vi o uso de funções Perl em alguns exemplos próximos do que estou procurando, mas não tenho idéia de onde / como usá-los.

BradH
fonte
3
Tem certeza de que deseja ter um hash do nome do arquivo e não do conteúdo do arquivo?
Anthon
12
Nota: O hash MD5 não é um dispositivo de criptografia. MD5 não é nem um hash criptográfico. Um hash, qualquer hash, é uma transformação unidirecional de um conjunto de dados em um número. Não é reversível. A criptografia real é reversível sempre (dada a chave usada para criptografar).
Kusalananda
1
fba8255e8e9ce687522455f3e1561e53é o hash MD5 mynicepicture, isso significa que a extensão deve ser removida antes do hash?
Kusalananda
@dessert Quero dizer que não há beneficiário se você fizer md5sum <<<"file name"o file namearquivo existente ou não, porque é considerado uma string, exceto alimentá-lo com o nome dos arquivos existentes.
αғsнιη

Respostas:

14

Você não disse qual shell deseja usar, então estou assumindo o Bash - a resposta precisa de ajustes para funcionar com outros shells.

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

Versão do script:

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

Esse forloop simples pega todos os arquivos no diretório atual, calcula a soma md5 de seu nome e a gera. Use isso para verificar a funcionalidade, se você deseja começar a renomear, substitua o segundo echopor mv.

Explicações

  • echo -n "$i" | md5sum- calcule a soma md5 do nome completo do arquivo, incluindo a extensão do arquivo ( Piping ), para reduzir a alteração da extensão echo -n "$i"para um dos seguintes:

    ${i%%.*}
    sed 's/\..*//' <<< "$i"
    echo "$i" | sed 's/\..*//'
  • sum=$(…)- execute e salve a saída em $sum( substituição de comando )

  • ${sum%% *}- imprima tudo até o primeiro espaço ( substituição de parâmetro ), o mesmo que um dos seguintes:

    $(sed 's/ .*//' <<< "$sum")
    $(echo "$sum" | sed 's/ .*//')
  • ${i##*.} - gera tudo após o último ponto (Substituição de parâmetro), o mesmo que um dos seguintes:

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

Se você precisar renomear arquivos recursivamente em pastas diferentes, use findcom a -execopção

sobremesa
fonte
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 o md5sumutilitário GNU coreutils para calcular o hash MD5 a partir do nome base (extensão sans) de qualquer nome de caminho. A função auxiliar md5namefaz o cálculo real e produzirá o novo nome com caminho e extensão completos.

A md5namefunção usa awkpara montar o novo nome das partes do nome do caminho fornecido e o resultado de md5sum.

Exemplos da função em uso por si só:

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

... onde c9e89fa443d16da4b96ea858881320c9está o hash MD5 da string file name here.

Remova o echoscript na parte superior para renomear os arquivos. Você pode salvar a saída do script original em um arquivo (com o echolocal), se em algum momento precisar restaurar os nomes dos arquivos em seus originais.

Observe que executar isso duas vezes em um conjunto de arquivos calculará o hash MD5 dos hashes MD5 e que o nome do arquivo original se tornará irrecuperável, a menos que você faça anotações cuidadosas sobre quais arquivos são chamados depois de cada execução do script.

Kusalananda
fonte
Assim como um FYI, a awkparte pode ser substituída por while read sum dummy ; do printf "%s/%s.%s\n' $dir $sum $ext ; done ;Você precisa dummycapturar o '-'.
Robert Benson
@RobertBenson O problema é que os nomes de arquivos que contêm espaços seriam confusos.
Kusalananda
Boa decisão. Nomes de arquivos com espaços são maus. Eu gosto de awkmim mesmo e levei um tempo para uso bashutilitários em vez de system()noawk
Robert Benson
5

Com 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"' {} +

(remova -nquando estiver feliz).

Stéphane Chazelas
fonte
Surpreendente! Isso calcula a soma md5 do nome do arquivo sem a extensão. Agora, e o nome completo do arquivo? OP não disse se precisa ou não dele.
dessert
1
Ele não disse isso, mas o exemplo que ele dá é exatamente isso.
Robert Benson
2

Para uma AWKabordagem:

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)
           }'

findComandos modernos não exigem um diretório para a entrada .é assumida; portanto, o [Diretório] pode ser deixado em branco. O -type fúnico encontra arquivos, o que é útil, pois md5sumnão gosta de diretórios e alterar o nome do diretório durante a execução não seria uma boa idéia. Use -iname patternse você quiser apenas usar alguns arquivos, por exemplo -iname \*.dat, se o caso for importante, use em -namevez de -iname.

As match(...); sub(...)peças estão extraindo partes do nome do arquivo e substituindo-as na string de entrada. Observe que "^"e "$"[pre / ap] estão pendentes para impedir a substituição de uma sequência que pode repetir o caminho / extensão.

Substitua print(com)por system(com)para realmente executar a renomeação.

Se você deseja usar o md5sumarquivo real como um nome, pode usar o fato de que md5sumgera a soma e o nome do arquivo de entrada para algo como:

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

O while read sum filelevará 2 argumentos, os resultados do md5sumcomando e atribuir sume filevariáveis com eles. Como o espaço sumnão deve ter espaços, ele readdeve funcionar bem.

Obviamente, ele [echo]deve ser removido durante a execução, mas é sempre uma boa idéia ao testar qualquer alteração no script para testar a pesquisa antes da execução.

Isso tudo pressupõe que você esteja executando bash. Além disso, isso pode ser digitado como uma linha longa:

find -iname \*.jpg -exec md5sum '{}' ';' | while read sum file ; do mv "$file" "`dirname $file`/$sum".jpg ; done
Robert Benson
fonte
1
Parece que isso fará o hash do conteúdo dos arquivos. O OP queria fazer o hash do nome (sem extensão).
Kusalananda
Acho que ajudaria se eu lesse completamente a pergunta.
Robert Benson
2

Essa abordagem geralmente gosto de usar.

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

O comando "ls" produz um fluxo de linhas de texto. O comando "sed" transforma cada linha com regras de correspondência de padrões. O comando "sed" gera um comando "mv" que é então canalizado através de um shell "sh" para execução. Os parâmetros do comando "mv" são como "mv oldfilename newfilename", que renomeia o arquivo. Eu construo o novo nome de arquivo com um comando sed que toma a parte antes do último ponto e o ecoa na entrada do comando "md5sum" e, em seguida, pega apenas o hash da saída.

Percorrendo meu processo, primeiro liste os arquivos ('head -n 3' para ver apenas as 3 primeiras linhas):

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

Em seguida, pense em transformar com sed (ainda não canalizando nenhum comando gerado por meio de um 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

Existem três padrões de correspondência:

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

Eu quero usar sed para substituir um nome de arquivo de entrada por "mv filename NEWfilename", mas como estou canalizando comandos através de um shell, posso gerar comandos que obtêm o md5sum, como este

echo "1000-26092016" | md5sum
    55b18a6b0add4a318b0079e18512b4e8  -

para obter apenas o hash

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

Em um shell unix, podemos usar operadores de backtick (`some_command`) para executar um subcomando, portanto, por exemplo

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

De volta ao comando mv, quero que o sed produza "mv here there" com "there" substituído por um comando backtick para obter o md5sum. A cadeia dentro da cadeia de substituição sed começa assim

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

Mas está claramente criando o mesmo hash para cada nome de arquivo, pois o comando backticked está sendo executado antes que o sed veja a string. Para impedir que o shell execute o comando backtick para que o sed produza os backticks, precisamos acrescentar barras (também ao caractere de pipe), então novamente:

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

A saída também precisa que os nomes dos arquivos sejam citados em caso de espaços, portanto

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"

Então, vamos experimentar este, canalizando-o através de um shell:

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

Funcionou ? eu acho:

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

Aqui está uma abordagem para verificação cruzada; use a opção "ls" "-i" para gerar o nó i do sistema de arquivos unix (que não muda com "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"

Ou, usando o comando "colar" (pacote '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
fonte
0

Eu gosto da resposta de uma linha, mas ela quebra porque analisa o nome do arquivo. Eu também bati um pouco com sha hashes.

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

Eu acho que ele também retira os arquivos e os coloca na base de onde o comando foi inserido.

Obrigado.

GoofProg
fonte
1
Provavelmente, devemos nos referir à resposta de sua base.
Jeff Schaller