散列多个文件

时间:2009-12-03 18:00:19

标签: python perl bash hash batch-processing

  

问题规范:

     

给定一个目录,我想遍历目录及其非隐藏的子目录,
  并在非隐藏中添加一个漩涡哈希   文件名。
  如果脚本重新运行,它将用新的哈希替换旧哈希。

     
    

<filename>.<extension> ==&GT; <filename>.<a-whirlpool-hash>.<extension>

         

<filename>.<old-hash>.<extension> ==&GT; <filename>.<new-hash>.<extension>

  


  

问题:

     

a)你会怎么做?

     

b)在您可以使用的所有方法中,是什么让您的方法最合适?


  

判决:

     

谢谢大家,我选择了SeigeX的答案,因为它具有速度和便携性   它在经验上比其他bash变体更快,
  它在我的Mac OS X机器上无需更改即可使用。

13 个答案:

答案 0 :(得分:6)

已更新以修复:
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'的进程替换而不是管道,没有子shell以这种方式生成
  • 将目录作为参数处理并对其进行健全性检查
  • 使用$()而不是``命令替换符号,后者不推荐使用
  • 使用带空格的文件
  • 使用带换行符的文件
  • 使用具有多个扩展名的文件
  • 使用没有扩展名的文件
  • 不遍历隐藏目录
  • NOT 是否会跳过预先散列的文件,它会根据规范重新计算哈希值

测试树

$ 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

答案 1 :(得分:4)

#!/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的输出中删除第一个空格及其后的所有内容。
  • 使用正则表达式提取基本文件名,该正则表达式查找未跟随另一个斜杠的最后一个句点(以便目录名称中的句点不计入扩展名的一部分)。
  • 通过使用带有起始索引的子字符串作为基本文件名的长度来找到扩展名。

答案 2 :(得分:3)

需求的逻辑非常复杂,足以证明使用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哈希不在Python的stdlib中,但有支持它的纯Python和C扩展,例如,python-mhash

安装它:

$ sudo apt-get install python-mhash

使用它:

import mhash

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

输出:     cbdca4520cc5c131fc3a86109dd23fee2d7ff7be56636d398180178378944a4f41480b938608ae98da7eccbf39a4c79b83a8590c4cb1bace5bc638fc92b3e653


在Python中调用whirlpooldeep

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可以为需要根据哈希值跟踪文件集的问题提供杠杆作用。

答案 3 :(得分:3)

我对第一个答案并不满意,因为正如我在那里所说,这个问题看起来最好用perl解决。你已经在一个你的问题的编辑中说你在OS X机器上有你想要运行它的perl,所以我试了一下。

很难在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通用界面而不是具有不同输出格式的不同程序。

  • 实现所有其他要求:忽略隐藏文件(以及隐藏目录下的文件)。

  • 能够处理任何可能的文件名而不会出现错误或安全问题。 (有几个人在他们的shell脚本中得到了这个权利。)

  • 遵循遍历目录树的最佳实践,通过chdired到每个目录(就像我以前的答案,使用find -execdir)。这样可以避免PATH_MAX出现问题,并且在运行时重命名目录。

  • 巧妙处理以。结尾的文件名。 foo..txt ... - &gt; foo..hash.txt ...

  • 处理包含哈希值的旧文件名,但不重命名,然后重命名。 (它删除了被“。”字符包围的128个十六进制数字的任何序列。)在一切正确的情况下,不会发生磁盘写入活动,只读取每个文件。您当前的解决方案在已经正确命名的情况下运行两次mv,导致目录元数据写入。并且速度较慢,因为这是必须执行的两个过程。

  • 高效。没有程序是fork / execed,而大多数实际工作的解决方案最终都必须为每个文件提供一些东西。 Digest :: Whirlpool是使用本机编译的共享库实现的,因此它不是慢速的纯perl。这应该比在每个文件上运行程序更快,尤其是对于小文件。

  • Perl支持UTF-8字符串,因此带有非ascii字符的文件名应该不是问题。 (不确定UTF-8中的任何多字节序列是否可以包含单独表示ASCII'。'的字节。如果可能,则需要UTF-8识别字符串处理.sed不知道UTF-8 Bash的glob表达式可以。)

  • 易于扩展。当你把它放到一个真正的程序中,并且你想处理更多的角落情况时,你可以很容易地做到这一点。例如当您想要重命名文件但已存在散列命名的文件名时,决定该怎么做。

  • 良好的错误报告。但是,大多数shell脚本都会通过传递它们运行的​​prog中的错误来实现这一点。

答案 4 :(得分:2)

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

答案 5 :(得分:1)

您可能希望将结果存储在一个文件中,例如

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

答案 6 :(得分:1)

在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

答案 7 :(得分:1)

这是我对bash的看法。功能:跳过非常规文件;正确处理名称中带有奇怪字符(即空格)的文件;处理无扩展名的文件名;跳过已经散列过的文件,因此它可以重复运行(尽管如果在运行之间修改文件,它会添加新的哈希而不是替换旧的哈希)。我用md5 -q作为哈希函数写了它;你应该能够用其他任何东西替换它,只要它只输出哈希值,而不是像filename =&gt;那样。散列。

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

答案 8 :(得分:1)

whirlpool不是一个非常常见的哈希。您可能必须安装一个程序来计算它。例如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 |读取-a数组时,拆分为“。”字符,但我通常不会使用数组变量。我没有简单的方法在手册页中看到插入哈希作为倒数第二个数组索引,并向下推最后一个元素(文件扩展名,如果它有一个。)任何时候bash数组变量看起来很有趣,我知道现在是时候做我在perl做的事了!查看使用read的陷阱: 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'nuad {} +有虚拟arg,因为命令后的第一个arg在shell的位置参数中变为$ 0,而不是循环结束的“$ @”的一部分。我使用execdir而不是exec,因此我不必处理目录名称(或者当实际文件名都足够短时,对于具有长名称的嵌套目录超过PATH_MAX的可能性。)

-not -regex可防止将此操作两次应用于同一文件。虽然whirlpool是一个非常长的哈希,并且mv说文件名太长,如果我没有那个检查运行它两次。 (在XFS文件系统上。)

没有扩展名的文件获取basename.hash。我必须特别检查以避免附加尾随。或者将基本名称作为扩展名。 $ {@#。/}删除了在每个文件名前面放置的前导./,所以没有“。”在没有扩展名的文件的整个字符串中。

mv --no-clobber可能是GNU扩展。如果您没有GNU mv,如果您想避免删除现有文件,请执行其他操作(例如,您运行一次,将一些相同的文件添加到具有旧名称的目录中;您再次运行它。)OTOH,如果你想要这种行为,就把它拿出来。

即使文件名包含换行符(他们可以,您知道!)或任何其他可能的字符,我的解决方案也应该有用。在perl中它会更快更容易,但你要求shell。

wallenborn使用所有校验和制作一个文件的解决方案(而不是重命名原始文件)非常好,但效率低下。不要为每个文件运行一次md5sum,一次在尽可能多的文件上运行它,因为它适合命令行:

找到dir -type f -print0 | xargs -0 md5sum&gt; dir.md5 或者使用GNU find,内置xargs(注意+而不是';')  find dir -type f -exec md5sum {} +&gt; dir.md5

如果你只是使用find -print | xargs -d'\ n',你会被带有引号的文件名搞砸,所以要小心。如果您不知道有朝一日运行此脚本的文件,请始终尝试使用print0或-exec。这是特别的。如果文件名由不受信任的用户提供(即可能是服务器上的攻击媒介),则为true。

答案 9 :(得分:1)

嗯,有趣的问题。

尝试以下方法(mktest函数仅用于测试 - 用于bash的TDD!:)

编辑:

  • 添加了对漩涡哈希的支持。
  • 代码清理
  • 更好地引用文件名
  • 更改了测试部分的数组语法 - 现在应该可以使用大多数类似于korn的shell。请注意,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

答案 10 :(得分:1)

回答您的最新问题:

  

如果有人可以评论我如何避免使用我的BASH脚本查看隐藏目录,那将非常感激。

您可以使用

来避免使用find查找隐藏目录
find -name '.?*' -prune -o \( -type f -print0 \)

-name '.*' -prune将修剪“。”,并在不做任何事情的情况下停止。 :/

我仍然会推荐我的Perl版本。我更新了它......你可能仍然需要从CPAN安装Digest :: Whirlpool。

答案 11 :(得分:0)

使用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参数

周围的引号

答案 12 :(得分:0)

红宝石:

#!/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的第二个参数。