在终端/ bash脚本中将文件夹拆分为多个子文件夹

32

我有几个文件夹,每个文件夹都有15,000到40,000张照片。我想将它们拆分为子文件夹 - 每个子文件夹中包含2,000个文件。

有什么快速的方法可以在进行中创建需要的每个文件夹并移动所有文件吗?

目前我只能找到如何将一个文件夹中的前x个项目移动到预先存在的目录中。为了在具有20,000个项目的文件夹上使用它...我需要手动创建10个文件夹,并运行该命令10次。

ls -1  |  sort -n | head -2000| xargs -i mv "{}" /folder/

我尝试使用for循环,但是在使用mkdir命令创建文件夹时遇到了问题。即使解决了这个问题,我仍然需要让程序只为每20个文件(新组的开头)创建一个文件夹。但它目前会为每个文件都创建一个新文件夹。

那么... 我应该如何将大量文件轻松地移动到每个文件夹中任意数量的文件中?

希望能获得任何有用的帮助!

9个回答

46

尝试像这样:

for i in `seq 1 20`; do mkdir -p "folder$i"; find . -type f -maxdepth 1 | head -n 2000 | xargs -i mv "{}" "folder$i"; done

完整脚本版本:

#!/bin/bash

dir_size=2000
dir_name="folder"
n=$((`find . -maxdepth 1 -type f | wc -l`/$dir_size+1))
for i in `seq 1 $n`;
do
    mkdir -p "$dir_name$i";
    find . -maxdepth 1 -type f | head -n $dir_size | xargs -i mv "{}" "$dir_name$i"
done

给初学者:

  1. 创建一个新文件:vim split_files.sh
  2. 更新dir_sizedir_name的值以符合您的要求
    • 请注意,dir_name将附加数字
  3. 进入所需文件夹:cd my_folder
  4. 运行脚本:sh ../split_files.sh

谢谢你的回答。我还需要从名为irr的子文件夹中读取和写入数据。所以我的代码看起来像这样: for i in \seq 1 10`; do mkdir -p "folder$i/irr"; find . -maxdepth 2 -type f | head -n 4000 | xargs -i mv "{}" "folder$i/irr"; done` - cgl
1
对于Mac OS,需要进行一些调整才能使用xargs命令:find . -maxdepth 1 -type f | head -n $dir_size |xargs -J {} mv {} "$dir_name$i" - CharlesC
出现错误:xargs:非法选项--i - Gaurav Agrawal

23

这个解决方案 对我在MacOS上很有效:

i=0; for f in *; do d=dir_$(printf %03d $((i/100+1))); mkdir -p $d; mv "$f" $d; let i++; done
它会创建每个子文件夹包含100个元素。

8

这个解决方案可以处理带有空格和通配符的名称,并且可以轻松扩展以支持不那么直接的树形结构。它将在工作目录的所有直接子目录中查找文件,并将它们分类到这些目录的新子目录中。新目录将被命名为01等:

#!/bin/bash

maxfilesperdir=20

# loop through all top level directories:
while IFS= read -r -d $'\0' topleveldir
do
        # enter top level subdirectory:
        cd "$topleveldir"

        declare -i filecount=0 # number of moved files per dir
        declare -i dircount=0  # number of subdirs created per top level dir

        # loop through all files in that directory and below
        while IFS= read -r -d $'\0' filename
        do
                # whenever file counter is 0, make a new dir:
                if [ "$filecount" -eq 0 ]
                then
                        mkdir "$dircount"
                fi

                # move the file into the current dir:
                mv "$filename" "${dircount}/"
                filecount+=1

                # whenever our file counter reaches its maximum, reset it, and
                # increase dir counter:
                if [ "$filecount" -ge "$maxfilesperdir" ]
                then
                        dircount+=1
                        filecount=0
                fi
        done < <(find -type f -print0)

        # go back to top level:
        cd ..
done < <(find -mindepth 1 -maxdepth 1 -type d -print0)
< p >使用进程替换的< code >find-print0/read组合是从另一个问题中借鉴来的。

需要注意的是,简单的通配符可以处理各种奇怪的目录和文件名。但是对于多层目录的扩展不太容易。


6
下面的代码假设文件名不包含换行符、空格、制表符、单引号、双引号或反斜杠,并且文件名不以破折号开头。它还假设 IFS 没有被更改,因为它使用 while read 而不是 while IFS= read,并且变量没有加引号。在 Zsh 中添加 setopt shwordsplit
i=1;while read l;do mkdir $i;mv $l $((i++));done< <(ls|xargs -n2000)

以下代码假定文件名不包含换行符,也不以破折号开头。-n2000 一次性处理2000个参数,{#} 是作业的序列号。将 {#} 替换为 '{=$_=sprintf("%04d",$job->seq())=}'以将数字填充到四位数。
ls|parallel -n2000 mkdir {#}\;mv {} {#}

以下命令假定文件名不包含换行符,并使用Aristotle Pagaltzis的 rename 实现,它是Homebrew中的rename公式,其中需要使用-p来创建目录,需要使用--stdin从标准输入获取路径,以及$N表示文件编号。在其他实现中,您可以使用$.++$::i代替$N

ls|rename --stdin -p 's,^,1+int(($N-1)/2000)."/",e'

3
使用ls存在很多问题。我稍微改了一下您的示例代码:find . -type f -print0|parallel -0 -n2000 mkdir "dir_{#}"\;mv {} "dir_{#}"。对我来说同样有效。 - Pascal
@Pascal 不会,除非你的文件名中有换行符,或者你使用的是奇怪版本的 ls(比如 busybox 的 ls),即使输出不是到终端,也会将文件名中的字符替换为问号。 - nisetama

4
我建议采用如下方案:
#!/bin/bash
# outnum generates the name of the output directory
outnum=1
# n is the number of files we have moved
n=0

# Go through all JPG files in the current directory
for f in *.jpg; do
   # Create new output directory if first of new batch of 2000
   if [ $n -eq 0 ]; then
      outdir=folder$outnum
      mkdir $outdir
      ((outnum++))
   fi
   # Move the file to the new subdirectory
   mv "$f" "$outdir"

   # Count how many we have moved to there
   ((n++))

   # Start a new output directory if we have sent 2000
   [ $n -eq 2000 ] && n=0
done

“$n -eq 2000” 应该改为 “$n -eq 1999”,因为起始值是从零开始的。不过,无论如何,我喜欢这个解决方案,谢谢。 - ksclarke

2
上面的答案非常有用,但在Mac(10.13.6)终端中有一个非常重要的点。因为xargs“-i”参数不可用,我已将上面的命令更改为以下命令。 ls -1 | sort -n | head -2000| xargs -I '{}' mv {} /folder/
然后,我使用以下shell脚本(参考tmp的答案)。
#!/bin/bash

dir_size=500
dir_name="folder"
n=$((`find . -maxdepth 1 -type f | wc -l`/$dir_size+1))
for i in `seq 1 $n`;
do
    mkdir -p "$dir_name$i";
    find . -maxdepth 1 -type f | head -n $dir_size | xargs -I '{}' mv {} "$dir_name$i"
done

2
这是对马克·塞切尔的微调。
使用方法:
bash splitfiles.bash $PWD/directoryoffiles splitsize

它不需要脚本与拆分文件位于同一目录中,它可以操作所有文件,而不仅仅是 .jpg 文件,并且允许您指定拆分大小作为参数。

#!/bin/bash
# outnum generates the name of the output directory
outnum=1
# n is the number of files we have moved
n=0

if [ "$#" -ne 2 ]; then
    echo Wrong number of args
    echo Usage: bash splitfiles.bash $PWD/directoryoffiles splitsize
    exit 1
fi

# Go through all files in the specified directory
for f in $1/*; do
   # Create new output directory if first of new batch
   if [ $n -eq 0 ]; then
      outdir=$1/$outnum
      mkdir $outdir
      ((outnum++))
   fi
   # Move the file to the new subdirectory
   mv "$f" "$outdir"

   # Count how many we have moved to there
   ((n++))

   # Start a new output directory if current new dir is full
   [ $n -eq $2 ] && n=0
done

提前创建所有目录,你知道需要多少个。这样可以避免在循环中进行检查...虽然与文件系统移动操作相比可能微不足道。点赞。 - MrR
这是对我完美运行的一个。谢谢! - Denis.Kipchakbaev

1

可以直接在终端中运行

i=0; 
for f in *; 
do 
    d=picture_$(printf %03d $((i/2000+1))); 
    mkdir -p $d; 
    mv "$f" $d; 
    let i++; 
done

这个脚本将会把当前目录中的所有文件移动到picture_001、picture_002等文件夹中。每个新创建的文件夹将包含2000个文件。

  • 2000是分块数
  • %03d是后缀数字,您可以进行调整(当前为001、002、003)
  • picture_是文件夹前缀
  • 这个脚本将会把所有文件分块到它们所在的目录中(创建子目录)

0

你肯定需要为此编写一个脚本。

以下是你的脚本应包含的提示:

首先,计算源目录中文件的数量。

NBFiles=$(find . -type f -name *.jpg | wc -l)

将此计数除以2000并加1,以确定要创建的目录数。
NBDIR=$(( $NBFILES / 2000 + 1 ))

最后,遍历您的文件并将它们移动到子目录中。 您需要使用两个嵌套循环:一个用于选择和创建目标目录,另一个用于将2000个文件移动到此子目录中,然后创建下一个子目录并将下一个2000个文件移动到新的子目录中,以此类推...

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