多文件哈希处理

3

问题描述:

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

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

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


问题:

a)你会怎样做?

b)在所有可用的方法中,什么使你的方法最合适?


结论:

感谢大家,我选择了SeigeX的答案,因为它速度快且具有可移植性。
它在我的Mac OS X机器上不需要修改即可正常工作。


最好使用Whirlpool作为加密哈希算法,因为它是一种强大的密码哈希算法,并且是公共领域哈希。可以通过安装md5deep来安装Whirlpool,该软件还会安装whirlpooldeep。我打算在实现自由分发和开源的P2P和文件管理工具时使用哈希码。我是一名C和Java编程人员,拥有计算机科学专业背景,但不幸的是,由于这个原因,我往往会过度设计,而这个问题的最简单有效的解决方案超出了我的经验范围。 :p - Ande Turner
1
在你之前的编辑中,你说过你正在考虑用于P2P应用程序。你可能不想重命名用户的文件,因此你可以将哈希保存在数据库中,或者使用你当前的计划,但是使用硬链接而不是重命名原始文件。 - Peter Cordes
1
你问题中提供的解决方案会访问 '.*'(隐藏)目录。这是需要的吗? - jfs
2
该解决方案无法处理没有扩展名的文件(由于正则表达式从名称中删除哈希值)。 - jfs
我认为你最后的修改没有正确引用,导致它出现了问题;例如应该是find "${TARGET}"。此外,在[[中,$1将被视为glob模式,而不是普通字符串。尽管甚至“*”也无法匹配空字符串,所以我猜它可以工作。但我会把它整理好的。 - Peter Cordes
显示剩余2条评论
13个回答

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;使用内置参数扩展代替
  • 使用进程替换而不是管道进行“查找”,这样不会创建子shell
  • 将要处理的目录作为参数并对其进行检查
  • 使用$()而不是``符号进行命令替换,后者已被弃用
  • 适用于带空格的文件
  • 适用于带换行符的文件
  • 适用于具有多个扩展名的文件
  • 适用于没有扩展名的文件
  • 不遍历隐藏目录
  • 不跳过预先哈希的文件,它将根据规范重新计算哈希值

测试目录树

$ 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

你只需要使用-d检查,因为你需要路径包含/,对吧?否则你可以找到"$@"。但是,如果其中包含裸文件名,那么你将得到dirname="$file"。 - Peter Cordes
我有3个文件的文件名中带有 "[" 或 "]",在重新散列时出了些问题。 - Ande Turner
@ande_turner: 感谢你发现了这个问题。再试一次,我相信你会惊喜的发现它已经正常运行了。我现在使用偏移参数扩展来确定扩展名,因此文件名的内容不应该再干扰了。我还修复了“md5sum”在文件中出现换行符或反斜杠时,在哈希值中添加“\” 的问题。现在无论你输入什么样的内容,都应该可以正常工作了。 - SiegeX
@Peter:不,我使用-d是因为如果用户没有将目录作为参数传递,那么这可能是他们的错误。如果不希望出现这种行为,并且您确实只想在一个文件上运行它,那么您可以安全地从健全性检查中删除'! [[ -d "$1" ]]'. - SiegeX
非常棒的解决方案。我从阅读中学到了很多Bash技巧。已加入书签! - Mark Byers

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

对于你的第一个版本:filename=${file%.*} ... extension=${file##$filename} ... echo mv "$file" "$filename.$md5sum$extension" - Dennis Williamson
1
我认为你提出的更改不会有帮助。它将无法处理不带扩展名的文件,而这些文件位于包含句点的目录中。 - Mark Byers
如果目录名和文件名相似,则无法确定它们的位置。 - Dennis Williamson
1
这个解决方案在我的测试目录树上生成了错误的文件名(对于'f^Jnewline.ext1'文件)。请参见https://dev59.com/CErSa4cB1Zd3GeqPX6PC#1842682。 - jfs
这个解决方案也没有遵循规范,因为它从不重新对已经哈希过的文件进行哈希。如果文件内容发生更改,则需要更新文件的哈希值。 - SiegeX
1
当我发布它时,它遵循了规范并超出了规范的要求。 :) 我认为规范一定已经改变了。 - Mark Byers

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)

Here's a test directory tree. It contains:
- files without extension inside directories with a dot in their name - filename which already has a hash in it (test on idempotency) - filename with two extensions - newlines in names
``` $ tree a a |-- b | `-- c.d | |-- f | |-- f.ext1.ext2 | `-- g.d41d8cd98f00b204e9800998ecf8427e |-- c.ext^Mnewline | `-- f `-- f^Jnewline.ext1
7 directories, 5 files ```

Result

``` $ 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 ```
The solution works correctly for all cases.
Whirlpool hash is not in Python's stdlib, but there are both pure Python and C extensions that support it e.g., `python-mhash`.
To install it:
$ 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可以帮助解决需要基于哈希值跟踪一组文件的问题。


1
再次回到家,再次回到家!晚上好,J.F! - Ande Turner
@_ande_turner_:1. 你可以从源代码编译它 http://labix.org/python-mhash 2. 使用纯Python的whirlpool.py http://www.bjrn.se/code/whirlpoolpy.txt import whirlpool; print Whirlpool("text to hash").hexdigest() - jfs
  1. 从Python中调用whirlpooldeep。我已经在答案中添加了一个示例。
- jfs

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 );

优点: - 实际使用了whirlpool,所以您可以直接使用这个程序(在安装libperl-digest-whirlpool之后)。易于更改为任何您想要的摘要函数,因为您有Perl Digest通用接口,而不是具有不同输出格式的不同程序。
  • 实现了所有其他要求:忽略隐藏文件(以及隐藏目录下的文件)。

  • 能够处理任何可能的文件名,没有错误或安全问题。(几个人在他们的shell脚本中做到了这一点)。

  • 按照最佳实践遍历目录树,通过chdiring进入每个目录(与我的先前答案一样,使用find -execdir)。这避免了PATH_MAX和在运行时重命名目录的问题。

  • 聪明地处理以.结尾的文件名。foo..txt... -> foo..hash.txt...

  • 处理旧文件名包含哈希的情况,而无需重命名它们然后再将其重命名回来。(它会去掉由“.”字符包围的128个十六进制数字序列。)在一切正确的情况下,不会发生磁盘写入活动,只需要读取每个文件。您当前的解决方案在已正确命名的情况下运行mv两次,导致目录元数据写入。而且速度更慢,因为那是必须执行的两个进程。

  • 高效。没有fork/exec程序,而大多数实际可行的解决方案最终都必须对每个文件进行某种形式的sed操作。Digest::Whirlpool使用原生编译的共享库实现,因此它不是纯Perl的慢速。这应该比在每个文件上运行程序要快,尤其是对于小文件。

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

  • 易于扩展。当您将其放入真正的程序中并且想要处理更多角落情况时,您可以很容易地做到这一点。例如:决定重命名文件但哈希命名的文件名已经存在时该怎么办。

  • 良好的错误报告。大多数shell脚本都有这个功能,通过传递来自运行的程序的错误。


我将这段代码复制到了 ~/whirlpool-rename.pl 文件中,然后将其设置为可执行文件,移动到我的测试文件夹中并运行它。但是它返回了一个错误信息:'invalid top directory at /System/Library/Perl/5.10.0/File/Find.pm line 593.' 我不知道如何检查是否已安装 libperl-digest-whirlpool - Ande Turner
在程序中加入 "use Digest::Whirlpool;" 这一行代码。如果你没有这个包,程序会在这一点上失败。这种情况下,你需要安装它,可能使用 CPAN 或者 fink(如果 fink 有提供该包)。(在 OS X 上可以使用 cpan 命令) - Peter Cordes
关于 "/System/.../File/Find.pm 上的无效顶级目录",那是当你没有任何参数运行它时发生的。您将空列表传递给了find()。我忘记把 "." 设为默认要递归进入的目录。我会进行编辑。 - Peter Cordes
好的,请尝试新版本。我还将使用Digest;更改为使用Digest::Whirlpool,因此如果Digest没有whirlpool,则会失败。 并在其中加入一条注释,说明您可以在哪里链接而不是重命名。 - Peter Cordes
嗯,你可以先使用cp -al命令,然后在硬链接农场上运行此命令。但这会失去不写入磁盘的优势,而且在perl脚本中处理它可能更加清晰。 - Peter Cordes
如果你无法让 CPAN 正常工作,你显然可以解析 Whirlpool 程序对每个文件的输出。 - Peter Cordes

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

3
  1. 无法处理文件名中包含空格的情况。
  2. 它会尝试重命名目录,这将导致它找不到那些目录中的文件...... 在第一行使用 find . -type f -print|while read file ,然后在 hash=mv 行中,给文件名加上引号。
- NVRAM
由于上述列出的原因,此方法无法正常工作,而且对于没有扩展名的文件也无效。我改进了您的想法,并提供了一个新的解决方案,可以解决大部分这些问题。 - Mark Byers

1

这是我在bash中的看法。 特点:跳过非常规文件;正确处理名称中带有奇怪字符(即空格)的文件;处理没有扩展名的文件名;跳过已经哈希的文件,因此可以重复运行(尽管如果文件在运行之间被修改,则添加新哈希而不是替换旧哈希)。 我使用md5 -q作为哈希函数编写它;只要它仅输出哈希而不是像文件名 => 哈希这样的东西,您就应该能够将其替换为其他任何内容。

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

Whirlpool并不是一种常见的哈希算法。你可能需要安装一个计算程序来计算它。例如,Debian/Ubuntu包括一个名为“whirlpool”的软件包。该程序仅将一个文件的哈希值打印出来。apt-cache搜索whirlpool显示,其他一些软件包也支持它,包括有趣的md5deep。

早期的一些回答可能无法处理文件名中包含空格的情况。如果是这种情况,但是您的文件名中没有换行符,则可以安全地使用\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="。"尝试一些东西,find -print0 | while read -a array,以便按"."字符拆分,但是我通常从不使用数组变量。 我在man手册中没有发现将哈希插入为倒数第二个数组索引并推下最后一个元素(如果有文件扩展名)的简单方法。 任何时候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' dummy {} + 中有dummy参数,因为命令后第一个参数将成为shell位置参数中的$0,而不是"$@"的一部分,循环遍历。我使用execdir而不是exec,这样我就不必处理目录名(或嵌套目录名称过长而超过PATH_MAX的可能性,当实际文件名都足够短时)。

-not -regex可以防止对同一文件应用两次。虽然whirlpool是一个非常长的哈希,如果我在XFS文件系统上运行两次而没有进行检查,则mv会显示“File name too long”。

没有扩展名的文件得到basename.hash。我必须特别检查以避免添加尾随点或将基名作为扩展名。${@#./}去除find在每个文件名前面加入的./。因此对于没有扩展名的文件,整个字符串中没有“。”。

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。特别是如果文件名由不受信任的用户提供(即可能成为服务器攻击向量)。


1
针对您更新的问题:
如果有人能够评论一下我如何在BASH脚本中避免查找隐藏目录,将不胜感激。
您可以通过使用 find 命令来避免查找隐藏目录。
find -name '.?*' -prune -o \( -type f -print0 \)

-name '.*' -prune 会剪枝“.”,并在不执行任何操作的情况下停止。 :/

不过我仍然建议使用我的 Perl 版本。我已经更新了它... 不过你可能仍需要从 CPAN 安装 Digest::Whirlpool。


请查看我的回答:https://dev59.com/CErSa4cB1Zd3GeqPX6PC#1880234 - SiegeX

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接