Pergunta

Problema Especificação:

Dado um diretório, eu quero percorrer os diretórios e sua não-escondido sub-diretórios,
e adicionar um hash de hidromassagem para o não-oculto nomes de arquivo.
Se o script for re-executá-lo seria iria substituir um hash velho por um novo.

<filename>.<extension> ==> <filename>.<a-whirlpool-hash>.<extension>

<filename>.<old-hash>.<extension> ==> <filename>.<new-hash>.<extension>


Pergunta:

a) Como você faria isso?

b) Dos todos os métodos disponíveis para você, o que faz o seu método mais adequado?


Veredicto:

Obrigado a todos, eu vos escolhi a resposta de SeigeX por sua velocidade e portabilidade.
É emprically mais rápido do que a outra festa variantes,
e funcionou sem alteração na minha máquina Mac OS X.

Foi útil?

Solução

Atualizado para correção:
1. Os nomes de arquivos com '[' ou ']' em seu nome (na verdade, qualquer personagem agora. Veja comentário)
2. Manipulação de md5sum quando hashing um arquivo com uma barra invertida ou de nova linha em seu nome
3. Functionized de hash de verificação de algo para modularidade
4. lógica de verificação de hash reformulado para remover duplo negativos

#!/bin/bash
if (($# != 1)) || ! [[ -d "$1" ]]; then
    echo "Usage: $0 /path/to/directory"
    exit 1
fi

is_hash() {
 md5=${1##*.} # strip prefix
 [[ "$md5" == *[^[:xdigit:]]* || ${#md5} -lt 32 ]] && echo "$1" || echo "${1%.*}"
}

while IFS= read -r -d $'\0' file; do
    read hash junk < <(md5sum "$file")
    basename="${file##*/}"
    dirname="${file%/*}"
    pre_ext="${basename%.*}"
    ext="${basename:${#pre_ext}}"

    # File already hashed?
    pre_ext=$(is_hash "$pre_ext")
    ext=$(is_hash "$ext")

    mv "$file" "${dirname}/${pre_ext}.${hash}${ext}" 2> /dev/null

done < <(find "$1" -path "*/.*" -prune -o \( -type f -print0 \))

Este código tem as seguintes vantagens sobre outras entradas até agora

  • É totalmente compatível com Bash versões 2.0.2 e além
  • Nenhuma chamada supérfluos para outros binários como sed ou grep; usos embutidas expansão de parâmetros em vez
  • Usa substituição processo para 'encontrar' em vez de um tubo, nenhum sub-shell é feito desta maneira
  • É de diretório para trabalhar em como um argumento e faz uma verificação de sanidade nele
  • Usa $ () em vez de `` notação para substituição de comando, o último é obsoleto
  • Funciona com arquivos com espaços
  • Funciona com arquivos com novas linhas
  • Funciona com arquivos com várias extensões
  • Funciona com arquivos com nenhuma extensão
  • não atravessar diretórios ocultos
  • não ignorar arquivos pré-hash, ele vai recalcular o hash conforme a especificação

Árvore Teste

$ tree -a a
a
|-- .hidden_dir
|   `-- foo
|-- b
|   `-- c.d
|       |-- f
|       |-- g.5236b1ab46088005ed3554940390c8a7.ext
|       |-- h.d41d8cd98f00b204e9800998ecf8427e
|       |-- i.ext1.5236b1ab46088005ed3554940390c8a7.ext2
|       `-- j.ext1.ext2
|-- c.ext^Mnewline
|   |-- f
|   `-- g.with[or].ext
`-- f^Jnewline.ext

4 directories, 9 files 

Resultado

$ tree -a a
a
|-- .hidden_dir
|   `-- foo
|-- b
|   `-- c.d
|       |-- f.d41d8cd98f00b204e9800998ecf8427e
|       |-- g.d41d8cd98f00b204e9800998ecf8427e.ext
|       |-- h.d41d8cd98f00b204e9800998ecf8427e
|       |-- i.ext1.d41d8cd98f00b204e9800998ecf8427e.ext2
|       `-- j.ext1.d41d8cd98f00b204e9800998ecf8427e.ext2
|-- c.ext^Mnewline
|   |-- f.d41d8cd98f00b204e9800998ecf8427e
|   `-- g.with[or].d41d8cd98f00b204e9800998ecf8427e.ext
`-- f^Jnewline.d3b07384d113edec49eaa6238ad5ff00.ext

4 directories, 9 files

Outras dicas

#!/bin/bash
find -type f -print0 | while read -d $'\0' file
do
    md5sum=`md5sum "${file}" | sed -r 's/ .*//'`
    filename=`echo "${file}" | sed -r 's/\.[^./]*$//'`
    extension="${file:${#filename}}"
    filename=`echo "${filename}" | sed -r 's/\.md5sum-[^.]+//'`
    if [[ "${file}" != "${filename}.md5sum-${md5sum}${extension}" ]]; then
        echo "Handling file: ${file}"
        mv "${file}" "${filename}.md5sum-${md5sum}${extension}"
    fi
done
  • Testado em arquivos contendo espaços como 'um b'
  • Testado em arquivos contendo várias extensões como 'a.b.c'
  • Testado com diretórios contendo espaços e / ou pontos.
  • Testado em arquivos contendo nenhuma extensão dentro diretórios que contêm pontos, tais como 'a.b / c'
  • Atualização :. Agora atualiza hashes se as alterações de arquivos

Pontos principais:

  • Use of print0 canalizada para while read -d $'\0', para lidar corretamente com espaços em nomes de arquivo.
  • md5sum pode ser substituído com seu função hash favorito. As remove sed o primeiro espaço e tudo depois da saída do md5sum.
  • O nome de arquivo base é extraído usando uma expressão regular que encontra o último período em que não é seguida por outra barra (de modo que os períodos em nomes de diretório não são contados como parte da extensão).
  • A extensão é encontrado usando uma subsequência com índice de partida como o comprimento do nome do ficheiro de base.

A lógica dos requisitos é bastante complexo para justificar o uso de Python em vez de bash. Ele deve fornecer uma solução mais legível, extensível e de fácil manutenção.

#!/usr/bin/env python
import hashlib, os

def ishash(h, size):
    """Whether `h` looks like hash's hex digest."""
    if len(h) == size: 
        try:
            int(h, 16) # whether h is a hex number
            return True
        except ValueError:
            return False

for root, dirs, files in os.walk("."):
    dirs[:] = [d for d in dirs if not d.startswith(".")] # skip hidden dirs
    for path in (os.path.join(root, f) for f in files if not f.startswith(".")):
        suffix = hash_ = "." + hashlib.md5(open(path).read()).hexdigest()
        hashsize = len(hash_) - 1
        # extract old hash from the name; add/replace the hash if needed
        barepath, ext = os.path.splitext(path) # ext may be empty
        if not ishash(ext[1:], hashsize):
            suffix += ext # add original extension
            barepath, oldhash = os.path.splitext(barepath) 
            if not ishash(oldhash[1:], hashsize):
               suffix = oldhash + suffix # preserve 2nd (not a hash) extension
        else: # ext looks like a hash
            oldhash = ext
        if hash_ != oldhash: # replace old hash by new one
           os.rename(path, barepath+suffix)

Aqui está uma árvore de diretórios de teste. Ele contém:

  • arquivos sem extensão dentro diretórios com um ponto em seu nome
  • filename que já tem um hash nela (teste na idempotency)
  • nome de arquivo com duas extensões
  • novas linhas em nomes
$ tree a
a
|-- b
|   `-- c.d
|       |-- f
|       |-- f.ext1.ext2
|       `-- g.d41d8cd98f00b204e9800998ecf8427e
|-- c.ext^Mnewline
|   `-- f
`-- f^Jnewline.ext1

7 directories, 5 files

Resultado

$ tree a
a
|-- b
|   `-- c.d
|       |-- f.0bee89b07a248e27c83fc3d5951213c1
|       |-- f.ext1.614dd0e977becb4c6f7fa99e64549b12.ext2
|       `-- g.d41d8cd98f00b204e9800998ecf8427e
|-- c.ext^Mnewline
|   `-- f.0bee89b07a248e27c83fc3d5951213c1
`-- f^Jnewline.b6fe8bb902ca1b80aaa632b776d77f83.ext1

7 directories, 5 files

A solução funciona corretamente para todos os casos.


Whirlpool hash não é em stdlib do Python, mas não são ambos puros extensões Python e C que a suportam, por exemplo, python-mhash.

Para instalá-lo:

$ sudo apt-get install python-mhash

Para usá-lo:

import mhash

print mhash.MHASH(mhash.MHASH_WHIRLPOOL, "text to hash here").hexdigest()

Saída: cbdca4520cc5c131fc3a86109dd23fee2d7ff7be56636d398180178378944a4f41480b938608ae98da7eccbf39a4c79b83a8590c4cb1bace5bc638fc92b3e653


Invocando whirlpooldeep em Python

from subprocess import PIPE, STDOUT, Popen

def getoutput(cmd):
    return Popen(cmd, stdout=PIPE, stderr=STDOUT).communicate()[0]

hash_ = getoutput(["whirlpooldeep", "-q", path]).rstrip()

git pode fornecer com alavancagem para os problemas que precisam controlar conjunto de arquivos com base em seus hashes.

Eu não estava muito feliz com minha primeira resposta, pois, como eu disse lá, este problema parece que é melhor resolvido com perl. Você já disse em uma edição da sua pergunta que você tem perl na máquina OS X que você deseja executar este no, então eu dei-lhe um tiro.

É difícil obtê-lo bem em bash, ou seja, evitando quaisquer problemas citando com nomes estranhos, e se comportar bem com nomes de arquivos canto de caso.

Então aqui está em perl, uma solução completa para o seu problema. Corre-se sobre todos os arquivos / diretórios listados na linha de comando.


#!/usr/bin/perl -w
# whirlpool-rename.pl
# 2009 Peter Cordes <peter@cordes.ca>.  Share and Enjoy!

use Fcntl;      # for O_BINARY
use File::Find;
use Digest::Whirlpool;

# find callback, called once per directory entry
# $_ is the base name of the file, and we are chdired to that directory.
sub whirlpool_rename {
    print "find: $_\n";
#    my @components = split /\.(?:[[:xdigit:]]{128})?/; # remove .hash while we're at it
    my @components = split /\.(?!\.|$)/, $_, -1; # -1 to not leave out trailing dots

    if (!$components[0] && $_ ne ".") { # hidden file/directory
        $File::Find::prune = 1;
        return;
    }

    # don't follow symlinks or process non-regular-files
    return if (-l $_ || ! -f _);

    my $digest;
    eval {
        sysopen(my $fh, $_, O_RDONLY | O_BINARY) or die "$!";
        $digest = Digest->new( 'Whirlpool' )->addfile($fh);
    };
    if ($@) {  # exception-catching structure from whirlpoolsum, distributed with Digest::Whirlpool.
        warn "whirlpool: couldn't hash $_: $!\n";
        return;
    }

    # strip old hashes from the name.  not done during split only in the interests of readability
    @components = grep { !/^[[:xdigit:]]{128}$/ }  @components;
    if ($#components == 0) {
        push @components, $digest->hexdigest;
    } else {
        my $ext = pop @components;
        push @components, $digest->hexdigest, $ext;
    }

    my $newname = join('.', @components);
    return if $_ eq $newname;
    print "rename  $_ ->  $newname\n";
    if (-e $newname) {
        warn "whirlpool: clobbering $newname\n";
        # maybe unlink $_ and return if $_ is older than $newname?
        # But you'd better check that $newname has the right contents then...
    }
    # This could be link instead of rename, but then you'd have to handle directories, and you can't make hardlinks across filesystems
    rename $_, $newname or warn "whirlpool: couldn't rename $_ -> $newname:  $!\n";
}


#main
$ARGV[0] = "." if !@ARGV;  # default to current directory
find({wanted => \&whirlpool_rename, no_chdir => 0}, @ARGV );

Vantagens: - na verdade, usa hidromassagem, de modo que você pode usar este programa exato diretamente. (Após a instalação libperl-digerir-de hidromassagem). Fácil de mudança para qualquer função digerir você quer, porque em vez de diferentes programas com diferentes formatos de saída, você tem a perl interface comum Digest.

  • implementos todos os outros requisitos:. Ignorar arquivos escondidos (e arquivos sob diretórios ocultos)

  • capaz de lidar com qualquer possível nome do arquivo sem erro ou problema de segurança. (Várias pessoas tem esse direito em seus shell scripts).

  • segue as melhores práticas para atravessar uma árvore de diretórios, por chdiring para baixo em cada diretório (como a minha resposta anterior, com find -execdir). Isso evita problemas com PATH_MAX, e com diretórios sendo renomeado enquanto você está correndo.

  • manipulação inteligente de nomes de arquivos que terminam com. foo..txt ... -> foo..hash.txt ...

  • Handles nomes de arquivos antigos contendo hashes já sem renomeá-los e, em seguida, renomeá-los de volta. (Ele remove qualquer sequência de 128 dígitos hexadecimais que está rodeado por "" caracteres.) No caso tudo correto, nenhuma atividade de gravação em disco acontecer, basta lê de cada arquivo. Seus atuais corridas solução mv duas vezes no caso já corretamente-nomeado, causando escreve diretório de metadados. E ser mais lento, porque é dois processos que têm de ser execced.

  • eficiente. Nenhum programa são fork / Execed, enquanto a maioria das soluções que realmente funcionam acabou tendo que algo sed por arquivo. Digest :: Whirlpool é implementado com um lib compartilhada nativamente-compilados, por isso não é lento puro-perl. Este deve ser mais rápido do que executar um programa em cada arquivo, esp. para arquivos pequenos.

  • Perl suporta UTF-8 cordas, de modo nomes de arquivos com caracteres não-ascii não deve ser um problema. (Não sei se quaisquer sequências multi-byte em UTF-8 pode incluir o byte que meios ASCII '' por conta própria. Se isso for possível, então você precisa UTF 8 manipulação de strings conscientes. Sed não sabe UTF-8 . expressões glob do Bash pode.)

  • facilmente extensível. Quando você vai para colocar isso em um programa real, e que pretende lidar com mais casos de canto, você pode fazê-lo com bastante facilidade. por exemplo. decidir o que fazer quando você deseja renomear um arquivo, mas o nome do arquivo chamado hash já existe.

  • bom relatório de erros. A maioria dos scripts shell tem isso, porém, passando junto erros dos progs que correm.

find . -type f -print | while read file
do
    hash=`$hashcommand "$file"`
    filename=${file%.*}
    extension=${file##*.}
    mv $file "$filename.$hash.$extension"
done

Você pode querer armazenar os resultados em um arquivo, como em

find . -type f -exec md5sum {} \; > MD5SUMS

Se você realmente quer um arquivo por Hash:

find . -type f | while read f; do g=`md5sum $f` > $f.md5; done

ou mesmo

find . -type f | while read f; do g=`md5sum $f | awk '{print $1}'`; echo "$g $f"> $f-$g.md5; done

Aqui está a minha opinião sobre ele, em bash. Características: ignora os arquivos não-regulares; corretamente lida com arquivos com caracteres estranhos (ou seja, espaços) em seus nomes; lida com nomes de arquivos sem extensão; ignora os arquivos já hash, para que ele possa ser executado repetidamente (embora se arquivos são modificados entre as execuções, ele adiciona o novo hash em vez de substituir o antigo). Eu o escrevi utilizando q md5 como a função hash; você deve ser capaz de substituir isto por qualquer outra coisa, contanto que ele só gera o hash, não algo como filename => hash.

find -x . -type f -print0 | while IFS="" read -r -d $'\000' file; do
    hash="$(md5 -q "$file")" # replace with your favorite hash function
    [[ "$file" == *."$hash" ]] && continue # skip files that already end in their hash
    dirname="$(dirname "$file")"
    basename="$(basename "$file")"
    base="${basename%.*}"
    [[ "$base" == *."$hash" ]] && continue # skip files that already end in hash + extension
    if [[ "$basename" == "$base" ]]; then
            extension=""
    else
            extension=".${basename##*.}"
    fi
    mv "$file" "$dirname/$base.$hash$extension"
done

Em sh ou bash, duas versões. Um limita-se a arquivos com extensões ...

hash () {
  #openssl md5 t.sh | sed -e 's/.* //'
  whirlpool "$f"
}

find . -type f -a -name '*.*' | while read f; do
  # remove the echo to run this for real
  echo mv "$f" "${f%.*}.whirlpool-`hash "$f"`.${f##*.}"
done

Testing ...

...
mv ./bash-4.0/signames.h ./bash-4.0/signames.whirlpool-d71b117a822394a5b273ea6c0e3f4dc045b1098326d39864564f1046ab7bd9296d5533894626288265a1f70638ee3ecce1f6a22739b389ff7cb1fa48c76fa166.h
...

E esta versão mais complexa processa todos os arquivos texto, com ou sem extensões, com ou sem espaços e caracteres estranhos, etc, etc ...

hash () {
  #openssl md5 t.sh | sed -e 's/.* //'
  whirlpool "$f"
}

find . -type f | while read f; do
  name=${f##*/}
  case "$name" in
    *.*) extension=".${name##*.}" ;;
    *)   extension=   ;;
  esac
  # remove the echo to run this for real
  echo mv "$f" "${f%/*}/${name%.*}.whirlpool-`hash "$f"`$extension"
done

Whirlpool não é um hash muito comum. Você provavelmente terá que instalar um programa para calculá-lo. por exemplo. Debian / Ubuntu inclui um pacote de "redemoinho". O programa imprime o hash de um arquivo por si só. apt-cache search hidromassagem mostra que alguns outros pacotes de apoiá-lo, incluindo o md5deep interessante.

Alguns dos anwsers anteriores falhará em nomes de arquivos com espaços entre eles. Se este for o caso, mas seus arquivos não têm quaisquer novas linhas no nome do arquivo, então você pode usar com segurança \ n como um delimitador.


oldifs="$IFS"
IFS="
"
for i in $(find -type f); do echo "$i";done
#output
# ./base
# ./base2
# ./normal.ext
# ./trick.e "xt
# ./foo bar.dir ext/trick' (name "- }$foo.ext{}.ext2
IFS="$oldifs"

tentar sem definir IFS para ver porquê.

Eu ia tentar algo com IFS = ""; encontrar -print0 | enquanto ler variedade -a, para dividir em "" caracteres, mas eu normalmente nunca usar variáveis ??de matriz. Não há nenhuma maneira fácil que eu veja na página man para inserir o hash como o segundo último índice array, e empurrar para baixo o último elemento (a extensão do arquivo, se ele tinha um.) Quaisquer variáveis ??de matriz festa tempo parece interessante, eu sei é hora de fazer o que estou fazendo em perl em vez disso! Veja as armadilhas para o uso de leitura: http://tldp.org/LDP/abs/html/gotchas.html# BADREAD0

Eu decidi usar outra técnica que eu gosto: encontrar -exec sh -c. É o mais seguro, desde que você não está analisar nomes de arquivos.

Isso deve fazer o truque:


find -regextype posix-extended -type f -not -regex '.*\.[a-fA-F0-9]{128}.*'  \
-execdir bash -c 'for i in "${@#./}";do 
 hash=$(whirlpool "$i");
 ext=".${i##*.}"; base="${i%.*}";
 [ "$base" = "$i" ] && ext="";
 newname="$base.$hash$ext";
 echo "ext:$ext  $i -> $newname";
 false mv --no-clobber "$i" "$newname";done' \
dummy {} +
# take out the "false" before the mv, and optionally take out the echo.
# false ignores its arguments, so it's there so you can
# run this to see what will happen without actually renaming your files.

-execdir o bash -c 'cmd' fictício {} + tem o manequim arg lá porque o primeiro argumento após o comando torna-se $ 0 em parâmetros posicionais do shell, não faz parte do "$ @" que para laços mais. Eu uso execdir em vez de exec então eu não ter de lidar com nomes de diretório (ou a possibilidade de exceder PATH_MAX para diretórios aninhados com nomes longos, quando os nomes reais são todos curto o suficiente.)

-não -regex impede que isso seja aplicado duas vezes para o mesmo arquivo. Apesar de hidromassagem é um tempo extremamente longo hash e mv diz o nome do arquivo muito longo se eu executá-lo duas vezes sem que o check. (Em um sistema de arquivos XFS.)

Arquivos sem basename.hash extensão get. Eu tinha que verificar especialmente para evitar acrescentando um final., Ou recebendo o nome base como a extensão. ${@#./} retira o líder ./ que encontrar coloca na frente de cada nome de arquivo, por isso não existe "" em toda a cadeia de caracteres para arquivos com nenhuma extensão.

mv --no-clobber pode ser uma extensão GNU. Se você não tem GNU mv, fazer outra coisa se você quiser evitar a exclusão de arquivos existentes (por exemplo, você executar este uma vez, alguns dos mesmos arquivos são adicionados ao diretório com seus nomes antigos;. Você executá-lo novamente) OTOH, se você quiser que o comportamento, basta tirá-lo.

A minha solução deve funcionar mesmo quando os nomes de arquivo conter uma nova linha (que pode, você sabe!), Ou qualquer outro caractere possível. Seria mais rápido e mais fácil em perl, mas você pediu shell.

A solução da Wallenborn para fazer um arquivo com todas as somas de verificação (em vez de mudar o nome do original) é bom bonito, mas ineficiente. Não corra md5sum uma vez por arquivo, executá-lo em tantos arquivos de uma só vez como vai caber em sua linha de comando:

encontrar dir -type f -print0 | xargs -0 md5sum> dir.md5 ou com GNU find, xargs é construído em (note a + em vez de ';') encontrar dir -type f -exec md5sum {} +> dir.md5

Se você usar apenas encontrar -print | xargs -d '\ n', você será asneira por nomes de arquivo com aspas neles, então tome cuidado. Se você não sabe quais arquivos que você pode um dia executar este script em diante, sempre tentar usar print0 ou -exec. Esta é esp. verdadeiro se os nomes de arquivo são fornecidos por usuários não confiáveis ??(ou seja, pode ser um vetor de ataque em seu servidor.)

Em resposta à sua pergunta actualização:

Se alguém pode comentar sobre como eu posso evitar olhar em diretórios ocultos com a minha BASH script, ele seria muito apreciado.

Você pode evitar diretórios com achado escondido usando

find -name '.?*' -prune -o \( -type f -print0 \)

-name '.*' -prune vai podar "", e parar sem fazer nada. : /

Gostaria ainda recomendar a minha versão Perl, no entanto. I atualizado ... Você ainda pode precisar instalar Digest :: Whirlpool do CPAN, no entanto.

Hm, interessante problema.

Tente o seguinte (a função mktest é apenas para testar - TDD para a festança:!)

Editar:

  • Adicionado suporte para hidromassagem hashes.
  • limpeza de código
  • melhor citação de nomes de arquivos
  • mudou matriz sintaxe para part-- teste agora deve funcionar com a maioria dos shells Korn-like. Note-se que pdksh não suporta: expansão de parâmetros com base (ou melhor, isso significa outra coisa)

Note também que quando em modo-md5-lo falhar por nomes de arquivos com hidromassagem-como hashes, e possivelmente vice-versa.

#!/usr/bin/env bash

#Tested with:
# GNU bash, version 4.0.28(1)-release (x86_64-pc-linux-gnu)
# ksh (AT&T Research) 93s+ 2008-01-31
# mksh @(#)MIRBSD KSH R39 2009/08/01 Debian 39.1-4
# Does not work with pdksh, dash

DEFAULT_SUM="md5"

#Takes a parameter, as root path
# as well as an optional parameter, the hash function to use (md5 or wp for whirlpool).
main()
{
  case $2 in
    "wp")
      export SUM="wp"
      ;;
    "md5")
      export SUM="md5"
      ;;
    *)
      export SUM=$DEFAULT_SUM
      ;;
  esac

  # For all visible files in all visible subfolders, move the file
  # to a name including the correct hash:
  find $1 -type f -not -regex '.*/\..*' -exec $0 hashmove '{}' \;
}

# Given a file named in $1 with full path, calculate it's hash.
# Output the filname, with the hash inserted before the extention
# (if any) -- or:  replace an existing hash with the new one,
# if a hash already exist.
hashname_md5()
{
  pathname="$1"
  full_hash=`md5sum "$pathname"`
  hash=${full_hash:0:32}
  filename=`basename "$pathname"`
  prefix=${filename%%.*}
  suffix=${filename#$prefix}

  #If the suffix starts with something that looks like an md5sum,
  #remove it:
  suffix=`echo $suffix|sed -r 's/\.[a-z0-9]{32}//'`

  echo "$prefix.$hash$suffix"
}

# Same as hashname_md5 -- but uses whirlpool hash.
hashname_wp()
{
  pathname="$1"
  hash=`whirlpool "$pathname"`
  filename=`basename "$pathname"`
  prefix=${filename%%.*}
  suffix=${filename#$prefix}

  #If the suffix starts with something that looks like an md5sum,
  #remove it:
  suffix=`echo $suffix|sed -r 's/\.[a-z0-9]{128}//'`

  echo "$prefix.$hash$suffix"
}


#Given a filepath $1, move/rename it to a name including the filehash.
# Try to replace an existing hash, an not move a file if no update is
# needed.
hashmove()
{
  pathname="$1"
  filename=`basename "$pathname"`
  path="${pathname%%/$filename}"

  case $SUM in
    "wp")
      hashname=`hashname_wp "$pathname"`
      ;;
    "md5")
      hashname=`hashname_md5 "$pathname"`
      ;;
    *)
      echo "Unknown hash requested"
      exit 1
      ;;
  esac

  if [[ "$filename" != "$hashname" ]]
  then
      echo "renaming: $pathname => $path/$hashname"
      mv "$pathname" "$path/$hashname"
  else
    echo "$pathname up to date"
  fi
}

# Create som testdata under /tmp
mktest()
{
  root_dir=$(tempfile)
  rm "$root_dir"
  mkdir "$root_dir"
  i=0
  test_files[$((i++))]='test'
  test_files[$((i++))]='testfile, no extention or spaces'

  test_files[$((i++))]='.hidden'
  test_files[$((i++))]='a hidden file'

  test_files[$((i++))]='test space'
  test_files[$((i++))]='testfile, no extention, spaces in name'

  test_files[$((i++))]='test.txt'
  test_files[$((i++))]='testfile, extention, no spaces in name'

  test_files[$((i++))]='test.ab8e460eac3599549cfaa23a848635aa.txt'
  test_files[$((i++))]='testfile, With (wrong) md5sum, no spaces in name'

  test_files[$((i++))]='test spaced.ab8e460eac3599549cfaa23a848635aa.txt'
  test_files[$((i++))]='testfile, With (wrong) md5sum, spaces in name'

  test_files[$((i++))]='test.8072ec03e95a26bb07d6e163c93593283fee032db7265a29e2430004eefda22ce096be3fa189e8988c6ad77a3154af76f582d7e84e3f319b798d369352a63c3d.txt'
  test_files[$((i++))]='testfile, With (wrong) whirlpoolhash, no spaces in name'

  test_files[$((i++))]='test spaced.8072ec03e95a26bb07d6e163c93593283fee032db7265a29e2430004eefda22ce096be3fa189e8988c6ad77a3154af76f582d7e84e3f319b798d369352a63c3d.txt']
  test_files[$((i++))]='testfile, With (wrong) whirlpoolhash, spaces in name'

  test_files[$((i++))]='test space.txt'
  test_files[$((i++))]='testfile, extention, spaces in name'

  test_files[$((i++))]='test   multi-space  .txt'
  test_files[$((i++))]='testfile, extention, multiple consequtive spaces in name'

  test_files[$((i++))]='test space.h'
  test_files[$((i++))]='testfile, short extention, spaces in name'

  test_files[$((i++))]='test space.reallylong'
  test_files[$((i++))]='testfile, long extention, spaces in name'

  test_files[$((i++))]='test space.reallyreallyreallylong.tst'
  test_files[$((i++))]='testfile, long extention, double extention,
                        might look like hash, spaces in name'

  test_files[$((i++))]='utf8test1 - æeiaæå.txt'
  test_files[$((i++))]='testfile, extention, utf8 characters, spaces in name'

  test_files[$((i++))]='utf8test1 - 漢字.txt'
  test_files[$((i++))]='testfile, extention, Japanese utf8 characters, spaces in name'

  for s in . sub1 sub2 sub1/sub3 .hidden_dir
  do

     #note -p not needed as we create dirs top-down
     #fails for "." -- but the hack allows us to use a single loop
     #for creating testdata in all dirs
     mkdir $root_dir/$s
     dir=$root_dir/$s

     i=0
     while [[ $i -lt ${#test_files[*]} ]]
     do
       filename=${test_files[$((i++))]}
       echo ${test_files[$((i++))]} > "$dir/$filename"
     done
   done

   echo "$root_dir"
}

# Run test, given a hash-type as first argument
runtest()
{
  sum=$1

  root_dir=$(mktest)

  echo "created dir: $root_dir"
  echo "Running first test with hashtype $sum:"
  echo
  main $root_dir $sum
  echo
  echo "Running second test:"
  echo
  main $root_dir $sum
  echo "Updating all files:"

  find $root_dir -type f | while read f
  do
    echo "more content" >> "$f"
  done

  echo
  echo "Running final test:"
  echo
  main $root_dir $sum
  #cleanup:
  rm -r $root_dir
}

# Test md5 and whirlpool hashes on generated data.
runtests()
{
  runtest md5
  runtest wp
}

#For in order to be able to call the script recursively, without splitting off
# functions to separate files:
case "$1" in
  'test')
    runtests
  ;;
  'hashname')
    hashname "$2"
  ;;
  'hashmove')
    hashmove "$2"
  ;;
  'run')
    main "$2" "$3"
  ;;
  *)
    echo "Use with: $0 test - or if you just want to try it on a folder:"
    echo "  $0 run path (implies md5)"
    echo "  $0 run md5 path"
    echo "  $0 run wp path"
  ;;
esac

usando zsh:

$ ls
a.txt
b.txt
c.txt

A magia:

$ FILES=**/*(.) 
$ # */ stupid syntax coloring thinks this is a comment
$ for f in $FILES; do hash=`md5sum $f | cut -f1 -d" "`; mv $f "$f:r.$hash.$f:e"; done
$ ls
a.60b725f10c9c85c70d97880dfe8191b3.txt
b.3b5d5c3712955042212316173ccf37be.txt
c.2cd6ee2c70b0bde53fbe6cac3c8b8bb1.txt

desconstrução feliz!

Edit: arquivos adicionados em subdiretórios e aspas em torno argumento mv

Ruby:

#!/usr/bin/env ruby
require 'digest/md5'

Dir.glob('**/*') do |f|
  next unless File.file? f
  next if /\.md5sum-[0-9a-f]{32}/ =~ f
  md5sum = Digest::MD5.file f
  newname = "%s/%s.md5sum-%s%s" %
    [File.dirname(f), File.basename(f,'.*'), md5sum, File.extname(f)]
  File.rename f, newname
end

Handles nomes de arquivos que têm espaços, nenhuma extensão, e que já foram hash.

ignora escondidos arquivos e diretórios -. File::FNM_DOTMATCH add como o segundo argumento de glob se isso é desejado

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top