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

7

Sou muito novo no linux / linha de comando e preciso criptografar os nomes dos 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 eu estou preso em como obter o hash do nome do arquivo e, em seguida, renomear esse arquivo para o hash gerado mantendo a extensão ou seja

mynicepicture.jpg > fba8255e8e9ce687522455f3e1561e53.jpg 

Parece que deveria ser uma renomeação simples ou uma linha mv , mas não consigo entender isso.
Muito obrigado por suas percepções

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

    
por BradH 14.09.2017 / 07:07

6 respostas

13

Você não disse qual shell você quer usar, então estou assumindo 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 simples loop for pega todos os arquivos no diretório atual, calcula a soma md5 de seu nome e os gera. Use isto para verificar a funcionalidade, se você quiser começar a renomear, substitua o segundo echo por mv .

Explicações

  • echo -n "$i" | md5sum - calcula a soma md5 do nome completo do arquivo incluindo a extensão do arquivo ( Piping ), para remover 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%% *} - imprime tudo até o primeiro espaço ( Substituição de parâmetros ), o mesmo que um dos seguintes:

    $(sed 's/ .*//' <<< "$sum")
    $(echo "$sum" | sed 's/ .*//')
    
  • ${i##*.} - imprime 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 find com a opção -exec .

    
por 14.09.2017 / 07:32
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 script bash usa o utilitário md5sum do GNU coreutils para calcular o hash MD5 do nome base (extensão sans) de qualquer nome de caminho fornecido. A função auxiliar md5name faz o cálculo real e emitirá o novo nome com caminho e extensão completos.

A função md5name usa awk para montar o novo nome a partir 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 c9e89fa443d16da4b96ea858881320c9 é o hash MD5 da string file name here .

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

Note que rodar este duas vezes em um conjunto de arquivos irá calcular o hash MD5 de hashes MD5, e que o nome do arquivo original se torna irrecuperável a menos que você faça anotações cuidadosas sobre quais arquivos são chamados executar o script.

    
por 14.09.2017 / 09:46
5

Com perl 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 -n quando feliz).

    
por 14.09.2017 / 18:50
2

Para uma abordagem AWK :

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

Os comandos modernos find não requerem um diretório para entrada . é assumido, portanto, o [Diretório] pode ser deixado em branco. O -type f só encontra arquivos, o que é útil, pois md5sum não gosta de diretórios e alterar o nome do diretório durante a execução não seria uma boa ideia. Use -iname pattern se você quiser usar apenas alguns arquivos, por exemplo -iname \*.dat , se for importante, use -name em vez de -iname .

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

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

Se você quiser usar o md5sum do arquivo real como um nome, poderá usar o fato de que md5sum gera a soma e o nome do arquivo de entrada para fazer algo como:

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

O while read sum file receberá 2 argumentos, os resultados do comando md5sum e atribuirá sum e file variáveis a eles. Como o sum não deve ter espaços, o read deve funcionar bem.

Obviamente, o [echo] deve ser removido quando estiver sendo executado, mas é sempre uma boa ideia ao testar qualquer alteração com script para testar a pesquisa antes de executá-la.

Isso tudo supõe que você está 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
    
por 14.09.2017 / 18:42
2

Isso na abordagem eu geralmente gosto de usar.

ls | sed "s|^\(.*\)\.\([^\.]*\)$|mv . \'echo  \| md5sum \| cut -d' ' -f 1\'.|" | 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 então pega apenas o hash da sua saída.

Andando pelo 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 canalizar nenhum comando gerado por meio de um shell)

ls | sed "s|^\(.*\)\.\([^\.]*\)$|mv . .|" | 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 eu estou piping comandos através de um shell, eu 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 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 string dentro do sed replace-string começa assim

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

Mas está claramente fazendo o mesmo hash para cada nome de arquivo, já que o comando backticked está sendo executado antes que o sed veja a string. Para parar o shell executando o comando backtick, então sed irá mostrar os backticks, nós temos que preceder barras (também para o pipe-character), então novamente:

ls | sed "s|^\(.*\)\.\([^\.]*\)$|mv . \'echo  \| md5sum \| cut -d' ' -f 1\'.|" | 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 de nomes de arquivos para serem citados no caso de espaços, então

ls | sed "s|^\(.*\)\.\([^\.]*\)$|mv \".\" \"\'echo  \| md5sum \| cut -d' ' -f 1\'.\"|" | 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 \".\" \"\'echo  \| md5sum \| cut -d' ' -f 1\'.\"|" | 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 produzir o i-node do sistema de arquivos unix (que não muda com "mv"):

ls -1i | sort -n > .before
ls | sed "s|^\(.*\)\.\([^\.]*\)$|mv \".\" \"\'echo  \| md5sum \| cut -d' ' -f 1\'.\"|" | 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
    
por 15.09.2017 / 20:16
0

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

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

Acho que também puxa os arquivos e os coloca na base de onde o comando foi inserido.

Obrigado.

    
por 22.02.2018 / 12:16