我有一个充满文件的目录,这些文件根据文件名存储在子目录中,即:
20d1/d325/52d1/20d1d32552d1a95249e62662fbdf924dd72c4027.jpg
ccaf/13cf/3199/ccaf13cf319930e80f5f2ad02525b93e1326c160.jpg
ec07/53bd/2355/ec0753bd2355fa8ec5cf5163e219c162cce3b03a.jpg
...
正如您所看到的,文件名的前 12 个字符用于创建三层子目录。不幸的是,每个目录名称选择了四个字符,并且文件数量碰巧已经超出了文件系统上每个目录 32000 个条目的目录限制。所以他们需要重写为:
20d/1d3/255/2d1/20d1d32552d1a95249e62662fbdf924dd72c4027.jpg
cca/f13/cf3/199/ccaf13cf319930e80f5f2ad02525b93e1326c160.jpg
ec0/753/bd2/355/ec0753bd2355fa8ec5cf5163e219c162cce3b03a.jpg
...
因此每个目录三个字母而不是四个。有大量文件,因此该过程应该尽可能快。
我涉足过find
:
find /path/to/files -mindepth 4 -type f -regextype posix-extended -regex \
".*/([0-9a-f]{4}/){3}(([0-9a-f]{3})([0-9a-f]{3})([0-9a-f]{3})([0-9a-f]{3})[0-9a-f]+\.\w+)"
这可以很好地打印所有文件,但我不确定如何继续重写。我想在重写过程中使用正则表达式捕获组来重写路径$3/$4/$5/$6/$2
(对find
正则表达式的反向引用)。但find
似乎不支持类似的东西:
find ... -exec cp {} /elsewhere/$3/$4/$5/$6/$2 ;
处理这个问题的最佳方法是什么?sed
和的某种组合xargs
(我对此不太有经验)?我应该进行循环而不是find
执行操作吗?我有点失落。
答案1
要复制文件,您可以使用 find 和 GNU tar 的组合来完成这项工作:
$ find -type f ... -print0 \
| tar -c -f - --null --files-from - \
| tar -C DEST_BASE -v -x -f - \
--show-transformed \
--transform 's,PATTERN,REPLACE,OPTIONS
(find 生成所有源文件名,第一个 tar 将它们读入管道,第二个 tar 进行文件名/路径转换)
默认情况下,该--transform
选项需要一个基本的正则表达式,但也可以x
使用 regexp-option 。另一个有用的正则表达式选项是i
用于不区分大小写的匹配。
答案2
对于移动,您可以使用mmv
:
$ mmv -n ';????????????*.jpg' '#2#3#4/#5#6#7/#8#9#10/#11#12#13/#14.jpg'
20d1/d325/52d1/20d1d32552d1a95249e62662fbdf924dd72c4027.jpg
-> 20d/1d3/255/2d1/a95249e62662fbdf924dd72c4027.jpg
ccaf/13cf/3199/ccaf13cf319930e80f5f2ad02525b93e1326c160.jpg
-> cca/f13/cf3/199/30e80f5f2ad02525b93e1326c160.jpg
ec07/53bd/2355/ec0753bd2355fa8ec5cf5163e219c162cce3b03a.jpg
-> ec0/753/bd2/355/fa8ec5cf5163e219c162cce3b03a.jpg
(-n 仅用于报告和测试 - 文件实际上尚未移动)
不幸的是,mmv
没有“创建缺失目录”选项 - 因此,我们必须在实际移动之前执行此操作:
$ mmv -n ';????????????*.jpg' '#2#3#4/#5#6#7/#8#9#10/#11#12#13/#14.jpg' \
| sed 's,^.* -> \(.*/\)[^/]\+$,\1,' \
| xargs mkdir -p
mmv
使用 shell 通配符 - 不是正则表达式。该;
字符比较特殊,与源文件基路径匹配。反向引用通过 表示#n
。由于 shell 通配符不如扩展正则表达式强大,因此我使用了 12 个?
通配符来匹配文件名的前 12 个字符。
答案3
由于您有很多文件,因此需要注意命令行长度限制。另外,出于性能原因,您最好不要为每个文件启动一个新进程。
不要复制文件 - 这会花费大量时间,会使磁盘空间加倍,然后您会遇到删除原始文件而不删除副本的问题。移动文件,可靠多了。
虽然这可以使用 shell 实用程序来完成,但用 Perl、Python 或 Ruby 编写健壮且高效的脚本要容易得多。您不会遇到引用问题或需要拆分命令行。
Perl(在删除目录时省略错误检查):
#!/usr/bin/env perl
use warnings;
for my $dir1 (<*>) {
for my $dir2 (<$dir1/*>) {
for my $dir3 (<$dir2/*>) {
for my $file (<$dir3/*>) {
$file =~ m:.*/((...)(...)(...)(...).*):;
mkdir "$1";
mkdir "$1/$2";
mkdir "$1/$2/$3";
mkdir "$1/$2/$3/$4";
rename $file, "$1/$2/$3/$4/$file" or die "$file: $!";
}
rmdir $dir3;
}
rmdir $dir2;
}
rmdir $dir1;
}