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

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

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

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

有什么建议吗?

谢谢,

【问题讨论】:

    标签: linux shell file directory rename


    【解决方案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 目录进行一些维护时第一次犯错误。
      猜你喜欢
      • 1970-01-01
      • 2021-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-30
      • 1970-01-01
      • 2020-08-08
      • 2015-11-15
      相关资源
      最近更新 更多