mv 文件到通过正则表达式替换构造的不同路径

mv 文件到通过正则表达式替换构造的不同路径

我有一个充满文件的目录,这些文件根据文件名存储在子目录中,即:

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

相关内容