Linux:将 100 万个文件移动到基​​于前缀创建的文件夹中

Posted

技术标签:

【中文标题】Linux:将 100 万个文件移动到基​​于前缀创建的文件夹中【英文标题】:Linux: Move 1 million files into prefix-based created Folders 【发布时间】:2010-11-17 13:53:04 【问题描述】:

我有一个名为“images”的目录,其中包含大约一百万张图片。是的。

我想编写一个 shell 命令将所有这些图像重命名为以下格式:

原版: filename.jpg新版: /f/i/l/filename.jpg

有什么建议吗?

谢谢, 丹

【问题讨论】:

【参考方案1】:
for i in *.*; do mkdir -p $i:0:1/$i:1:1/$i:2:1/; mv $i $i:0:1/$i:1:1/$i:2:1/; done;

$i:0:1/$i:1:1/$i:2:1 部分可能是一个变量,或者更短或不同,但上面的命令可以完成工作。您可能会遇到性能问题,但如果您真的想使用它,请将 *.* 缩小到更少的选项(a*.*b*.* 或适合您的选项)

编辑:在 i 之前为 mv 添加了一个 $,正如 Dan 所指出的那样

【讨论】:

仅供参考,$i:0:1 语法是 bash-ism,这在 Linux 上可能没问题,但以防万一...... 如果文件夹中有几个目录,这个循环是否也会包含它们? 需要更正:for i in .;做 mkdir -p $i:0:1/$i:1:1/$i:2:1/; mv $i $i:0:1/$i:1:1/$i:2:1/;完成; 只有带有点的目录! 就我而言,我需要创建文件并将其移动到包含文件名前七个字符的目录。类似于 foo01, foo02, foo03 到目录 foo;和 bar01、bar02 到目录栏。我用$i:0:7/ 替换了$i:0:1/$i:1:1/$i:2:1/,它就像一个魅力。【参考方案2】:

您可以使用例如 sed 生成新文件名:

$ echo "test.jpg" | sed -e 's/^\(\(.\)\(.\)\(.\).*\)$/\2\/\3\/\4\/\1/'
t/e/s/test.jpg

所以,你可以这样做(假设所有目录都已创建):

for f in *; do
   mv -i "$f" "$(echo "$f" | sed -e 's/^\(\(.\)\(.\)\(.\).*\)$/\2\/\3\/\4\/\1/')"
done

或者,如果你不能使用 bash $( 语法:

for f in *; do
   mv -i "$f" "`echo "$f" | sed -e 's/^\(\(.\)\(.\)\(.\).*\)$/\2\/\3\/\4\/\1/'`"
done

但是,考虑到文件的数量,您可能只想使用 perl,因为要产生很多 sed 和 mv 进程:

#!/usr/bin/perl -w
use strict;

# warning: untested
opendir DIR, "." or die "opendir: $!";
my @files = readdir(DIR); # can't change dir while reading: read in advance
closedir DIR;
foreach my $f (@files) 
    (my $new_name = $f) =~ s!^((.)(.)(.).*)$!$2/$3/$4/$1/;
    -e $new_name and die "$new_name already exists";
    rename($f, $new_name);

perl 肯定仅限于同一文件系统,尽管您可以使用 File::Copy::move 来解决这个问题。

【讨论】:

哦,我注意到测试会发现一件事:需要测试“这是一个文件吗?”所以它不会移动目录。相当容易修复(例如,-f $f or next; 在 perl foreach 循环的顶部,类似于 shell 循环)【参考方案3】:

您可以将其作为 bash 脚本执行:

#!/bin/bash

base=base

mkdir -p $base/shorts

for n in *
do
    if [ $#n -lt 3 ]
    then
        mv $n $base/shorts
    else
        dir=$base/$n:0:1/$n:1:1/$n:2:1
        mkdir -p $dir
        mv $n $dir
    fi
done

不用说,您可能需要担心空格和短名称的文件。

【讨论】:

【参考方案4】:

我建议一个简短的 python 脚本。大多数 shell 工具都会拒绝这么多输入(尽管 xargs 可能会起作用)。将在几秒钟内更新示例。

#!/usr/bin/python
import os, shutil

src_dir = '/src/dir'
dest_dir = '/dest/dir'

for fn in os.listdir(src_dir):
  os.makedirs(dest_dir+'/'+fn[0]+'/'+fn[1]+'/'+fn[2]+'/')
  shutil.copyfile(src_dir+'/'+fn, dest_dir+'/'+fn[0]+'/'+fn[1]+'/'+fn[2]+'/'+fn)

【讨论】:

谢谢,看起来是个不错的解决方案。我需要等待文件传输到我的新服务器才能试用(预计 50 小时,哈哈)【参考方案5】:

任何在 shell 中使用通配符语法的建议解决方案都可能由于您拥有的文件数量过多而失败。在当前提出的解决方案中,perl 可能是最好的。

但是,您可以轻松地调整任何 shell 脚本方法来处理任意数量的文件,例如:

ls -1 | \
while read filename
do
  # insert the loop body of your preference here, operating on "filename"
done

我仍然会使用 perl,但如果您仅限于使用简单的 unix 工具,那么将上述 shell 解决方案之一与我展示的循环结合起来应该可以帮助您。不过会很慢。

【讨论】:

通配符语法应该没问题,它是一个内置的shell,它不是故意在命令行上传递给程序的(否则,命令行肯定会太长)。例如,seq 1 1000000 中的 i 有效。 我刚刚测试过:使用for f in * 可以很好地处理 1,000,000 个文件。慢,但它有效。 感谢您的评论,这很有帮助,因为我对 shell 脚本非常陌生 @derobert:感谢您对其进行测试并确认它确实有效。这显然是一个案例,从旧时代中吸取的教训不再一定是正确的。 Bash 显然改善了这方面。我知道它在 Bourne shell 下以各种方式失败,但那是在 80 年代末/90 年代初,当我在编写脚本对 NetNews 目录进行一些维护时第一次犯错误。

以上是关于Linux:将 100 万个文件移动到基​​于前缀创建的文件夹中的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的性能慢到爬行我将方法移动到基类中?

MySQL 最长前缀匹配 100 万条记录与 3000 种可能性

将数百万个文件从根 AZStorageBlob 复制到子文件夹

将前缀和后缀相同的文件移动到同一个目录的算法设计及C代码实现

如何实现最快的算法来匹配前缀与字符串?

如何使用适用于 Node.js 的 AWS 开发工具包将 Amazon S3 中的所有对象从一个前缀复制/移动到另一个前缀