Хеширование нескольких файлов - PullRequest
3 голосов
/ 03 декабря 2009

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

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

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

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


Вопрос:

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

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


Вердикт:

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

Ответы [ 13 ]

6 голосов
/ 10 декабря 2009

Обновлено для исправления:
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
4 голосов
/ 03 декабря 2009
#!/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.
  • Базовое имя файла извлекается с помощью регулярного выражения, которое находит последний период, за которым не следует еще один слеш (чтобы периоды в именах каталогов не учитывались как часть расширения).
  • Расширение найдено с использованием подстроки с начальным индексом в качестве длины основного имени файла.
3 голосов
/ 06 декабря 2009

Мне не очень понравился мой первый ответ, поскольку, как я уже сказал, эта проблема выглядит так, как будто ее лучше всего решить с помощью 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 );

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

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

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

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

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

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

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

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

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

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

3 голосов
/ 03 декабря 2009

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

2 голосов
/ 03 декабря 2009
find . -type f -print | while read file
do
    hash=`$hashcommand "$file"`
    filename=${file%.*}
    extension=${file##*.}
    mv $file "$filename.$hash.$extension"
done
1 голос
/ 08 декабря 2009

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

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

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

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

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

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

1 голос
/ 04 декабря 2009

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

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

Edit:

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

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

#!/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
1 голос
/ 03 декабря 2009

джакузи не очень распространенный хэш. Вам, вероятно, придется установить программу для его вычисления. например Debian / Ubuntu включают пакет «джакузи». Программа сама печатает хеш одного файла. apt-cache search whirlpool показывает, что некоторые другие пакеты поддерживают его, включая интересный md5deep.

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


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, чтобы понять, почему это важно.

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

Я решил использовать другую технику, которая мне нравится: find -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 в позиционных параметрах оболочки, а не частью "$ @", что для циклов over. Я использую execdir вместо exec, поэтому мне не нужно иметь дело с именами каталогов (или возможностью превышения PATH_MAX для вложенных директорий с длинными именами, когда фактические имена файлов достаточно короткие).

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

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

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

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

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

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

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

1 голос
/ 03 декабря 2009

Вот мой взгляд на 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
1 голос
/ 03 декабря 2009

В 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
...