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

6
我有一个名为“images”的目录,里面装满了大约一百万张图片。是的。
我想编写一个shell命令来将所有这些图像重命名为以下格式:
原始:`filename.jpg` 新的:`/f/i/l/filename.jpg`
有什么建议吗?
谢谢, 丹
5个回答

7
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*.* 或者适合你的选项)。编辑: 根据 Dan 的提示,在 mv 前加上 $ ,即 i 变成 $i

1
FYI,${i:0:1} 语法是 Bash 的特有语法,在 Linux 上可能没问题,但以防万一... - derobert
如果文件夹中有几个目录,这个循环也会将它们包括进去吗? - Dan
需要进行一处更正: 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; - Dan
只有包含点的目录! - Chris Huang-Leaver
在我的情况下,我需要创建并将文件移动到具有文件名前七个字符的目录中。类似于foo01、foo02、foo03到foo目录;bar01、bar02到bar目录。我用${i:0:7}/替换了${i:0:1}/${i:1:1}/${i:2:1}/,它完美地工作了。 - jdias

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来解决这个问题。


哦,我注意到一个测试可以发现的问题:需要有一个测试“这是一个文件吗?”以便它不会移动目录。修复起来相当容易(例如,在Perl foreach循环的顶部添加-f $f or next;,在shell循环中类似)。 - derobert

2
您可以将其作为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

毫无疑问,您可能需要担心空格和文件名过短的问题。

1

我建议使用一个简短的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小时哈哈)。 - Dan

0

任何在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内建命令,目的不是将其传递给程序(否则,命令行肯定会太长)。例如,使用类似于“for i in 'seq 1 1000000'”这样的语法是可以的。 - derobert
我刚刚测试了一下:使用for f in *可以很好地处理1,000,000个文件。虽然速度慢一些,但是它可以正常工作。 - derobert
谢谢您的评论,对我来说非常有帮助,因为我对Shell脚本编程非常新手。 - Dan
@derobert:感谢您测试并确认它确实有效。这显然是一个案例,旧日的经验教训不再一定正确。Bash 显然在这方面有所改进。我知道在 Bourne shell 下它以各种方式失败了,但那是在 80 年代末/90 年代初,当时我第一次犯了错误,写了一个脚本来对 NetNews 目录进行维护。 - Chris Cleeland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接