Python 将大目录拆分为子目录

Python 将大目录拆分为子目录,python,bash,perl,shell,command-line,Python,Bash,Perl,Shell,Command Line,我有一个目录,大约有250万个文件,超过70 GB 我想把它分成几个子目录,每个子目录中有1000个文件 以下是我尝试使用的命令: i=0; for f in *; do d=dir_$(printf %03d $((i/1000+1))); mkdir -p $d; mv "$f" $d; let i++; done 这个命令在小范围内对我有效,但我可以让它在这个目录上运行几个小时,而它似乎什么也不做 我愿意通过命令行以任何方式执行此操作:perl、python等。无论以何种方式完成此操作都

我有一个目录,大约有250万个文件,超过70 GB

我想把它分成几个子目录,每个子目录中有1000个文件

以下是我尝试使用的命令:

i=0; for f in *; do d=dir_$(printf %03d $((i/1000+1))); mkdir -p $d; mv "$f" $d; let i++; done
这个命令在小范围内对我有效,但我可以让它在这个目录上运行几个小时,而它似乎什么也不做


我愿意通过命令行以任何方式执行此操作:perl、python等。无论以何种方式完成此操作都是最快的…

我怀疑如果您进行检查,您会注意到您的程序实际上正在移动文件,尽管速度非常慢。启动一个程序是相当昂贵的(至少与进行系统调用相比),而且每个文件要执行三到四次!因此,以下速度应快得多:

perl -e'
   my $base_dir_qfn = ".";
   my $i = 0;
   my $dir;
   opendir(my $dh, $base_dir_qfn)
      or die("Can'\''t open dir \"$base_dir_qfn\": $!\n");

   while (defined( my $fn = readdir($dh) )) {
      next if $fn =~ /^(?:\.\.?|dir_\d+)\z/;

      my $qfn = "$base_dir_qfn/$fn";

      if ($i % 1000 == 0) {
         $dir_qfn = sprintf("%s/dir_%03d", $base_dir_qfn, int($i/1000)+1);
         mkdir($dir_qfn)
            or die("Can'\''t make directory \"$dir_qfn\": $!\n");
      }

      rename($qfn, "$dir_qfn/$fn")
         or do {
            warn("Can'\''t move \"$qfn\" into \"$dir_qfn\": $!\n");
            next;
         };

      ++$i;
   }
'

如果目录未被使用,我建议如下

find . -maxdepth 1 -type f | split -l 1000 -d -a 5 
这将创建n个名为x00000-x02500的文件(只是为了确保5位数字,尽管4位也可以)。然后可以将每个文件中列出的1000个文件移动到相应的目录中

可能
设置-o noclobber
,以消除名称冲突时发生重写的风险

要移动文件,可以更容易地使用大括号扩展来迭代文件名

for c in x{00000..02500}; 
do d="d$c"; 
   mkdir $d; 
   cat $c | xargs -I f mv f $d; 
done 

我将使用命令行中的以下命令:

find . -maxdepth 1 -type f |split -l 1000
for i in `ls x*`
do 
   mkdir dir$i
   mv `cat $i` dir$i& 2>/dev/null
done
键是执行每个mv语句的“&”

多亏了karakfa的拆分思想。

注意:是一条可行之路-它在一个进程中执行整个操作,因此比下面的Bash+标准实用程序解决方案快得多


基于bash的解决方案需要避免调用外部实用程序以合理执行的循环。
您自己的解决方案调用两个外部实用程序,并在每个循环迭代中创建一个子shell,这意味着您将总共创建大约750万个进程(!)

以下解决方案避免了循环,但是,考虑到输入文件的数量,仍然需要相当长的时间才能完成(每1000个输入文件将创建4个进程,即总共约10000个进程):

  • printf“%s\0”*
    打印目录中所有文件的NUL分隔列表。
    • 请注意,由于
      printf
      是Bash内置工具而不是外部实用程序,因此
      getconf ARG_max
      报告的最大命令行长度不适用
  • xargs-0-n1000
    使用1000个输入文件名的块调用指定的命令

    • 请注意,
      xargs-0
      是非标准的,但在Linux和BSD/OSX上都受支持
    • 使用NUL分隔输入可以可靠地传递文件名,而不必担心会无意中将其拆分为多个部分,甚至可以使用带有嵌入换行符的文件名(尽管这种文件名非常罕见)
  • bash-O nullglob-c
    在选项
    nullglob
    打开的情况下执行指定的命令字符串,这意味着不匹配的全局模式将扩展为空字符串

    • 命令字符串统计到目前为止创建的输出目录,以确定具有下一个更高索引的下一个输出目录的名称,创建下一个输出目录,并将当前批次(最多)1000个文件移动到该目录

这可能比Perl程序慢(1分钟用于10.000个文件),但它应该适用于任何兼容POSIX的shell

#! /bin/sh
nd=0
nf=0
/bin/ls | \
while read file;
do
  case $(expr $nf % 10) in
  0)
    nd=$(/usr/bin/expr $nd + 1)
    dir=$(printf "dir_%04d" $nd)
    mkdir $dir
    ;;
  esac
  mv "$file" "$dir/$file"
  nf=$(/usr/bin/expr $nf + 1)
完成

使用bash,可以使用算术扩展$(…)

当然,这个想法可以通过使用xargs来改进——对于250万个文件,不应该超过45秒

nd=0
ls | xargs -L 1000 echo | \
while read cmd;
do
  nd=$((nd+1))
  dir=$(printf "dir_%04d" $nd)
  mkdir $dir
  mv $cmd $dir
done

您的
*
需要一段时间才能展开。也许可以从一个更有针对性的文件名子集开始,比如
a*
,然后看看它是否会在更合理的时间内返回。您还可以考虑使用“find”而不是for循环。此外,我还担心在这个已经太大的目录中创建子目录。您考虑过在其他地方创建它们吗?我建议处理
find
的结果。只有编写良好的shell才能处理由多MB大小的命令或通配符扩展产生的字符串。读取程序中的目录既快捷又简单(请参阅@ikegami)。-如果您想使用shell脚本:在读取时通过管道
ls
将问题细分为
。。。完成
一个接一个地获取文件。@laune:让
*
for
循环中扩展到250万个文件名本身并不是问题-事实上,
for f in*;do…
在读取…时比ls快很多。问题是250万次调用多个外部实用程序。@mklement0您不希望每个文件都有一个mv。系数为1000时,可将其缩小到可管理的大小。-但由于最大命令长度为2.088.198(根据xargs--show limits),我认为扩展250万个*是行不通的所有这些都表明,这个任务对于shell脚本来说有点太多了。Perl已经足够好了,我们都知道使用C(甚至Java)。++,但是(a)使用GNU
find
生成的文件名列表将不会被排序,这与使用glob
*
不同,(b)假设OP的
xargs
支持
-0
,使用
tr'\n'\0'
会快得多;(c) 正如我在(b)中的代码片段中所做的那样,我建议双重引用变量引用以促进良好习惯,尽管这在这里不是绝对必要的。是的,文件不会被排序,但我不确定这是一项要求。这不是一项要求,但鉴于OP基于自己的方法
*
,差别值得一提。我刚刚意识到
-J
是一个BSD
xargs
选项;因此,对于GNU实用程序,必须使用更高效的命令
nd=0
ls | xargs -L 1000 echo | \
while read cmd;
do
  nd=$((nd+1))
  dir=$(printf "dir_%04d" $nd)
  mkdir $dir
  mv $cmd $dir
done