Вопрос

Спецификация проблемы:

Учитывая каталог, я хочу выполнить итерацию по каталогу и его не скрытым подкаталогам,
 и добавьте хэш whirlpool в имена не скрытых файлов.
Если скрипт будет запущен повторно, он заменит старый хэш на новый.

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

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


Вопрос:

а) Как бы вы это сделали?

б) Какой из всех доступных вам методов делает ваш метод наиболее подходящим?


Вердикт:

Спасибо всем, я выбрал ответ SeigeX из-за его скорости и переносимости.
Это эмпирически быстрее, чем другие варианты bash,
 и это работало без изменений на моем компьютере с Mac OS X.

Это было полезно?

Решение

Обновлено для исправления:
1.Имена файлов с '[' или ']' в названии (на самом деле, теперь любой символ.Смотрите комментарий)
2.Обработка md5sum при хэшировании файла с обратной косой чертой или переводом строки в его имени
3.Функциональный алгоритм проверки хэша на модульность
4.Реорганизованная логика проверки хэша для удаления двойных отрицаний

#!/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 \))

На данный момент этот код имеет следующие преимущества по сравнению с другими записями

  • Он полностью совместим с Bash версий 2.0.2 и выше
  • Никаких лишних вызовов других двоичных файлов, таких как sed или grep;вместо этого использует расширение встроенных параметров
  • Использует замену процесса для 'find' вместо канала, никакая вспомогательная оболочка не создается таким образом
  • Принимает каталог для работы в качестве аргумента и выполняет проверку его работоспособности
  • Использует $() вместо ` обозначения для подстановки команд, последнее устарело
  • Работает с файлами с пробелами
  • Работает с файлами с переводом строк
  • Работает с файлами с несколькими расширениями
  • Работает с файлами без расширения
  • Не просматривает скрытые каталоги
  • Делает НЕ пропустите предварительно хэшированные файлы, он пересчитает хэш в соответствии со спецификацией

Тестовое дерево

$ 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 

Результат

$ 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

Другие советы

#!/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
  • Тестировалось на файлах, содержащих пробелы типа "a b"
  • Тестировалось на файлах, содержащих несколько расширений, таких как 'a.b.c'
  • Тестировался с каталогами, содержащими пробелы и / или точки.
  • Тестировалось на файлах, не содержащих расширения, внутри каталогов, содержащих точки, таких как 'a.b /c'
  • Обновленный:Теперь обновляет хэши, если файл изменяется.

Ключевые моменты:

  • Использование print0 переданный по трубе в while read -d $'\0', для корректной обработки пробелов в именах файлов.
  • md5sum можно заменить вашей любимой хэш-функцией.Sed удаляет первый пробел и все, что находится после него, из выходных данных md5sum.
  • Базовое имя файла извлекается с помощью регулярного выражения, которое находит последнюю точку, за которой не следует другая косая черта (чтобы точки в именах каталогов не учитывались как часть расширения).
  • Расширение определяется с помощью подстроки с начальным индексом в качестве длины базового имени файла.

Логика требований достаточно сложна, чтобы оправдать использование Python вместо bash.Это должно обеспечить более читаемое, расширяемое и ремонтопригодное решение.

#!/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)

Вот тестовое дерево каталогов.Он содержит:

  • файлы без расширения внутри каталогов с точкой в их названии
  • имя файла, в котором уже есть хэш (тест на идемпотентность)
  • имя файла с двумя расширениями
  • новые строки в именах
$ tree a
a
|-- b
|   `-- c.d
|       |-- f
|       |-- f.ext1.ext2
|       `-- g.d41d8cd98f00b204e9800998ecf8427e
|-- c.ext^Mnewline
|   `-- f
`-- f^Jnewline.ext1

7 directories, 5 files

Результат

$ 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

Решение работает корректно во всех случаях.


Whirlpool hash отсутствует в stdlib Python, но существуют как чистые расширения Python, так и C, которые поддерживают его, например, python-mhash.

Чтобы установить его:

$ sudo apt-get install python-mhash

Чтобы использовать его:

import mhash

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

Выходной сигнал:cbdca4520cc5c131fc3a86109dd23fee2d7ff7be56636d398180178378944a4f41480b938608ae98da7eccbf39a4c79b83a8590c4cb1bace5bc638fc92b3e653


Вызывающий whirlpooldeep в 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 может предоставить рычаги для решения проблем, связанных с отслеживанием набора файлов на основе их хэшей.

Я не был действительно доволен своим первым ответом, поскольку, как я уже сказал там, эта проблема выглядит так, как будто ее лучше всего решать с помощью perl.Вы уже сказали в одной правке своего вопроса, что у вас есть perl на компьютере OS X, на котором вы хотите запустить это, поэтому я попробовал.

Трудно сделать все правильно в bash, т.е.избегая любых проблем с кавычками при нечетных именах файлов и хорошо работая с именами файлов в угловом регистре.

Итак, вот оно в perl, полное решение вашей проблемы.Он работает со всеми файлами / каталогами, перечисленными в его командной строке.


#!/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 );

Преимущества:- на самом деле использует whirlpool, так что вы можете использовать именно эту программу напрямую.(после установки libperl-digest-whirlpool).Легко переключается на любую функцию дайджеста, которую вы хотите, потому что вместо разных программ с разными форматами вывода у вас есть общий интерфейс perl Digest common.

  • реализует все остальные требования:игнорируйте скрытые файлы (и файлы в скрытых каталогах).

  • способен обрабатывать любое возможное имя файла без ошибок или проблем с безопасностью.(Несколько человек сделали это правильно в своих сценариях оболочки).

  • следует рекомендациям по обходу дерева каталогов, переходя в каждый каталог (как и в моем предыдущем ответе, с помощью find -execdir).Это позволяет избежать проблем с PATH_MAX и с переименованием каталогов во время работы.

  • умная обработка имен файлов , которые заканчиваются на .foo..txt ...-> foo..hash.txt...

  • Обрабатывает старые имена файлов, уже содержащие хэши, не переименовывая их, а затем переименовывая обратно.(Он удаляет любую последовательность из 128 шестнадцатеричных цифр, окруженную символами ".".) В случае "все правильно" операция записи на диск не выполняется, просто выполняется чтение каждого файла.Ваше текущее решение дважды запускает mv в уже правильно названном случае, вызывая запись метаданных каталога.И быть медленнее, потому что это два процесса, которые должны быть выполнены.

  • эффективный.Ни одна программа не разветвляется / не выполняется, в то время как большинство решений, которые действительно работали бы, заканчивались тем, что приходилось удалять что-то для каждого файла.Digest:: Whirlpool реализован с помощью изначально скомпилированной общей библиотеки, так что это не медленный чистый perl.Это должно быть быстрее, чем запускать программу для каждого файла, особенно.для небольших файлов.

  • Perl поддерживает строки UTF-8, поэтому имена файлов с символами, отличными от ascii, не должны быть проблемой.(не уверен, могут ли какие-либо многобайтовые последовательности в UTF-8 включать байт, который сам по себе означает ASCII '.'.Если это возможно, то вам нужна обработка строк с поддержкой UTF-8.sed не знает UTF-8.Глобальные выражения Bash могут.)

  • легко расширяемый.Когда вы собираетесь внедрить это в реальную программу и хотите обрабатывать больше угловых случаев, вы можете сделать это довольно легко.например ,решите, что делать, если вы хотите переименовать файл, но имя файла с хэш-именем уже существует.

  • хорошие отчеты об ошибках.Однако большинство сценариев оболочки имеют это, передавая ошибки из программ, которые они запускают.

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

Возможно, вы захотите сохранить результаты в одном файле, например, в

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

Если вам действительно нужен один файл на хэш:

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

или даже

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

Вот мой взгляд на это, в bash.Характеристики:пропускает нестандартные файлы;корректно работает с файлами со странными символами (т.е.пробелы) в их названиях;имеет дело с именами файлов без расширения;пропускает уже хэшированные файлы, поэтому его можно запускать повторно (хотя, если файлы изменяются между запусками, он добавляет новый хэш, а не заменяет старый).Я написал это, используя md5 -q в качестве хэш-функции;вы должны быть в состоянии заменить это чем-либо другим, при условии, что оно выводит только хэш, а не что-то вроде 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

В sh или bash - две версии.Можно ограничиться файлами с расширениями...

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

Тестирование...

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

И эта более сложная версия обрабатывает все обычные файлы, с расширениями или без них, с пробелами и нечетными символами или без них и т.д. и т.п...

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 - не очень распространенный хэш.Вероятно, вам придется установить программу для его вычисления.например ,Debian / Ubuntu включают в себя пакет "whirlpool".Программа сама выводит хэш одного файла.поиск в apt-кэше whirlpool показывает, что некоторые другие пакеты поддерживают его, включая интересный md5deep.

Некоторые из более ранних запросов завершатся ошибкой при использовании имен файлов с пробелами в них.Если это так, но в имени файла ваших файлов нет новых строк, то вы можете смело использовать в качестве разделителя.


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"

попробуйте не задавать "ЕСЛИ", чтобы понять, почему это важно.

Я собирался попробовать что-нибудь с IFS=".";find -print0 | при чтении -массив, для разделения на символы ".", но обычно я никогда не использую переменные массива.На странице руководства я не вижу простого способа вставить хэш в качестве предпоследнего индекса массива и нажать на последний элемент (расширение файла, если оно у него было).) Каждый раз, когда переменные массива bash выглядят интересными, я знаю, что пришло время сделать то, что я делаю в perl вместо этого!Смотрите рекомендации по использованию read:http://tldp.org/LDP/abs/html/gotchas.html#BADREAD0

Я решил использовать другую технику, которая мне нравится:найти -exec sh -c.Это самый безопасный способ, поскольку вы не анализируете имена файлов.

Это должно сработать:


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 bash -c 'cmd' dummy {} + содержит фиктивный аргумент, потому что первый аргумент после команды становится $ 0 в позиционных параметрах оболочки, а не частью "$ @", который for повторяет цикл.Я использую execdir вместо exec, поэтому мне не приходится иметь дело с именами каталогов (или возможностью превышения PATH_MAX для вложенных каталогов с длинными именами, когда все фактические имена файлов достаточно короткие.)

-not -regex предотвращает повторное применение этого параметра к одному и тому же файлу.Хотя whirlpool - это чрезвычайно длинный хэш, и mv выдает слишком длинное имя файла, если я запускаю его дважды без этой проверки.(в файловой системе XFS.)

Файлы без расширения получают базовое имя.hash.Мне пришлось специально проверить, чтобы избежать добавления завершающего символа ., или получения базового имени в качестве расширения.${@#./} удаляет начальный символ ./, который find помещает перед каждым именем файла, поэтому во всей строке нет "." для файлов без расширения.

mv --no-clobber может быть расширением GNU.Если у вас нет GNU mv, сделайте что-нибудь еще, если хотите избежать удаления существующих файлов (напримервы запускаете это один раз, и некоторые из тех же файлов добавляются в каталог со своими старыми именами;запустите его снова.) OTOH, если вам нужно такое поведение, просто удалите его.

Мое решение должно работать, даже если имена файлов содержат новую строку (вы знаете, они могут!) или любой другой возможный символ.В perl это было бы быстрее и проще, но вы попросили shell.

решение Валленборна для создания одного файла со всеми контрольными суммами (вместо переименования оригинала) довольно хорошее, но неэффективное.Не запускайте md5sum один раз для каждого файла, запустите его сразу для стольких файлов, сколько поместится в его командной строке:

найдите каталог -введите f -print0 | xargs -0 md5sum > dir.md5 или с помощью GNU find, xargs встроен (обратите внимание на + вместо ';') найдите dir -введите f -exec md5sum {} + > dir.md5

если вы просто используете find -print | xargs -d'\ n' , вы будете сбиты с толку именами файлов, заключенными в кавычки, так что будьте осторожны.Если вы не знаете, с какими файлами вы могли бы когда-нибудь запустить этот скрипт, всегда старайтесь использовать print0 или -exec .Это экстрасенс.значение true, если имена файлов предоставлены ненадежными пользователями (т. е.это может быть вектор атаки на ваш сервер.)

В ответ на ваш обновленный вопрос:

Если кто-нибудь может прокомментировать, как я могу избежать поиска в скрытых каталогах с помощью моего скрипта BASH, я был бы очень признателен.

Вы можете избежать скрытых каталогов с помощью функции find, используя

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

-name '.*' -prune подрежу ".", и остановлюсь, ничего не делая.:/

Тем не менее, я бы все равно рекомендовал свою версию Perl.Я обновил его...Однако вам все равно может потребоваться установить Digest::Whirlpool из CPAN.

Хм, интересная проблема.

Попробуйте следующее (функция mktest предназначена только для тестирования -- TDD для bash!:)

Редактировать:

  • Добавлена поддержка хэшей whirlpool.
  • очистка кода
  • лучшее цитирование имен файлов
  • изменен синтаксис массива для тестовой части - теперь он должен работать с большинством korn-подобных оболочек.Обратите внимание, что pdksh не поддерживает расширение параметров на основе :- (или, скорее, это означает что-то другое)

Обратите также внимание, что в режиме md5 происходит сбой для имен файлов с хэшами, подобными whirlpool, и возможно, наоборот.

#!/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

использование zsh:

$ ls
a.txt
b.txt
c.txt

Волшебство:

$ 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

Счастливой деконструкции!

Редактировать:добавлены файлы в подкаталоги и заключены в кавычки mv аргумент

Рубин:

#!/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

Обрабатывает имена файлов, которые содержат пробелы, без расширения и которые уже были хэшированы.

Игнорирует скрытые файлы и каталоги — добавить File::FNM_DOTMATCH в качестве второго аргумента glob если это желательно.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top