在linux中根据文件名将大量文件移动到目录中

在linux中根据文件名将大量文件移动到目录中

我在 Linux 服务器的目录中有大量具有以下名称模式的文件:

1_file.txt
2_file.txt
3_file.txt
...
1455728_file.txt

有没有办法移动前 100000 个文件(1_file.txt 至 100000_file.txt) 进入目录1_100000,第二个 100000 个文件(100001_file.txt 至 200000_file.txt) 进入目录100001_200000, 等等 ... ?

答案1

未经测试

我会做类似的事情:

#!/bin/bash
bottom=0
while [[ $bottom -lt 150000 ]] ; do
    myfirst=$((bottom + 1))
    mylast=$((bottom + 100000))
    bottom=$((bottom + 100000))

    dir="${myfirst}_$mylast"
    [[ -d "$dir" ]] || mkdir "$dir"
    seq $myfirst $mylast | \
        while read p ; do
            q="${p}_file.txt"
            [[ -f "$q" ]] && echo "$q"
        done | \
            xargs --no-run-if-empty  echo mv -t "$dir"

done

当你想真正做到这一点时,请删除它echoecho mv

答案2

脚本文件

#!/bin/bash

step=100000
file_dir=$1

# Counting of files in the directory
shopt -s nullglob
file_list=("${file_dir}"/*)
file_num=${#file_list[@]}

# Every file's common part
suffix='_file.txt'

for((from = 1, to = step; from <= file_num; from += step, to += step)); do
    new_dir="${from}_${to}"
    mkdir "${file_dir}/${new_dir}"

    if ((to > file_num)); then
        to="$file_num"
    fi
    
    # Generating filenames by `seq` command and passing them to `xargs`
    seq -f "${file_dir}/%.f${suffix}" "$from" "$to" | xargs mv -t "${file_dir}/${new_dir}"
done

用法:./script.sh files

测试

我已经通过此命令生成了文件:

printf '%s\0' files/{1..1455728}_file.txt | xargs -0 touch

然后做:

$ time ./script.sh files

# Time is:
real    10m43,618s
user    0m9,953s
sys 0m19,671s

相当慢。

结果

$ ls -1v files
1_100000
100001_200000
200001_300000
300001_400000
400001_500000
500001_600000
600001_700000
700001_800000
800001_900000
900001_1000000
1000001_1100000
1100001_1200000
1200001_1300000
1300001_1400000
1400001_1500000

答案3

在 shell 中可以进行算术运算,但它总是很尴尬,因此我建议您寻找另一种脚本语言来完成这里的大部分工作。以下用途awk,但您也可以perl同样使用。我想说的是,您也可以python在下面的示例中轻松使用,但是python的语法方面使得如何将 python 脚本内联嵌入到像这样的管道中并不明显。 (这是可以完成的,但是非常棘手。)请注意,我不用来awk执行实际的移动,只是进行生成所需目标目录所需的计算。如果您使用perlpython,它们也可以执行文件系统操作。

一些假设:

  • 您想要移动具有完整原始名称的文件。修改脚本以去掉原始的数字前缀并不困难(尽管文件最好不全部以 结尾_file.txt)。

  • _文件名中只有一个且没有空格。如果情况并非如此,类似下面的内容仍然可以工作,但您需要在 awk 脚本和后面的 shell 循环中更加小心。

因此,鉴于这些,以下内容应该有效。

ls | 
awk -F_ '
{
    n = $1 - 1               # working zero based is easier here
    base = n - (n % 100000)  # round down to the nearest multiple of 100,000
    printf "%d_%d %s_%s\n", base + 1, base + 100000, $1, $2
}' |
while read destdir orig
do
    mkdir -p $destdir 
    mv $orig $destdir
done

那么,这是怎么回事?

ls | ...

这仅列出文件名,并且由于输出将发送到管道而不是终端,因此它每行列出一个文件名。文件将按ls默认顺序排序,但脚本的其余部分不关心这一点,并且可以使用随机的文件名列表正常工作。

... | awk -F_ '
{
    n = $1 - 1               # working zero based is easier here
    base = n - (n % 100000)  # round down to the nearest multiple of 100,000
    printf "%d_%d %s_%s\n", base + 1, base + 100000, $1, $2
} | ...'

这并不复杂,但如果你awk以前没有玩过,理解起来有点困难。首先,这里的目标是一次读取一个文件名ls,然后为每个文件名生成一个包含两个字段的输出行:第一个字段具有原始文件名的适当目标目录,第二个字段传递原始文件名文件名,以便管道的以下部分可以使用它。所以,更详细地说,

  • 标志-F_告诉awk它将每个输入行分割成_字符上的字段。假设_这些文件名中仅出现一次,awk 将分配$1给名称的数字部分$2以及_.然后,如刚刚描述的那样施加$1并设置支撑块。$2

  • 的计算base确定该文件属于 100000 个文件中的哪一个块。首先,通过从文件名的初始数字中n减去来进行计算。1此数字以零为基数,这样可以更轻松地使用下一行中使用的模算术。接下来,向下舍n入到最接近的 100,000 倍数。如果n已经是 100,000 的倍数,则不受影响。 (如果您不熟悉“%”运算符,它会计算除以 时的N % M余数。因此,、等等。)NM5 % 3 == 26 % 3 == 0

  • 最后,printf组装管道下一级所需的输出线。它生成一条包含两个字段的行,两个字段之间用空格分隔。第一个是目标目录的名称,通过使用base导出目录名称的上下限部分生成;在这里,返回到基于 1 的输出计数方案。第二个字段是重建的原始输入文件名。

... | while read destdir orig
do
    mkdir -p $destdir && mv $orig $destdir
done

这是管道的最后阶段,实际上完成了所有动作。它将脚本生成的每一行读取awk为两个字段,然后

  • 它确保目录存在,使用mkdir -p(如果目录已经存在,则不执行任何操作),
  • 如果成功,它将原始文件移动到新目录。

mkdir ... && mv ...在 shell 脚本中使用该模式通常是一个好主意,因为如果mkdir由于任何原因失败,则不会尝试重命名。

这种多个管道阶段的模式,每个阶段都以某种简单但有用的方式增量转换数据,是编写多种 shell 脚本的非常有效的方法。它发挥了 shell 在进程和管道控制方面的优势,同时允许您将 shell 不擅长的更复杂的计算推入更合适的语言中。

答案4

改编自我的答案给你的相关问题:

#! /bin/zsh -

zmodload zsh/files # makes mv and a few other file manipulation commands builtin
batch=10000

highest=(<1->_file.txt(n[-1]))
highest=${highest%%_*}

for ((start = 1; start <= highest; start += batch)); do
  (( end = start + batch - 1))
  files=(<$start-$end>_file.txt(N))
  if (($#files)); then
    mkdir -p ${start}_${end} || exit
    mv -- $files ${start}_${end}/ || exit
  fi
done

相关内容