我在 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
当你想真正做到这一点时,请删除它echo
。echo 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
执行实际的移动,只是进行生成所需目标目录所需的计算。如果您使用perl
或python
,它们也可以执行文件系统操作。
一些假设:
您想要移动具有完整原始名称的文件。修改脚本以去掉原始的数字前缀并不困难(尽管文件最好不全部以 结尾
_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
余数。因此,、等等。)N
M
5 % 3 == 2
6 % 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