移动文件始终是一个挑战。在我看来,到目前为止提出的所有解决方案都存在破坏文件的风险。这可能是因为这个挑战听起来很简单,但在实现时需要考虑和测试的内容很多。
我们也不能低估解决方案的效率,因为我们可能要处理(非常)大量的文件。
这里是一个经过仔细和深入测试自己文件的脚本。但当然,使用时需自行承担风险!
这个解决方案:
- 对包含空格的文件名是安全的。
- 不使用 xargs -L,因为这很容易导致“参数列表过长”错误。
- 基于 Bash 4,不依赖于 awk、sed、tr 等工具。
- 随着要移动的文件数量的增加而扩展得很好。
以下是代码:
if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then
echo "$(basename "$0") requires Bash 4+"
exit -1
fi >&2
opt_dir=${1:-.}
opt_max=1000
readarray files <<< "$(find "$opt_dir" -maxdepth 1 -mindepth 1 -type f)"
moved=0 dirnum=0 dirname=''
for ((i=0; i < ${#files[@]}; ++i))
do
if [[ $((i % opt_max)) == 0 ]]; then
((dirnum++))
dirname="$opt_dir/$(printf "%02d" $dirnum)"
fi
file=${files[$i]::-1}
if [[ -n $file ]]; then
[[ -d $dirname ]] || mkdir -v "$dirname" || exit
mv "$file" "$dirname" || exit
((moved++))
fi
done
echo "moved $moved file(s)"
例如,将此保存为
split_directory.sh。现在假设您有2001个文件在
some/dir中:
$ split_directory.sh some/dir
mkdir: created directory some/dir/01
mkdir: created directory some/dir/02
mkdir: created directory some/dir/03
moved 2001 file(s)
现在的新情况如下:
- some/dir 包含 3 个目录和 0 个文件
- some/dir/01 包含 1000 个文件
- some/dir/02 包含 1000 个文件
- some/dir/03 包含 1 个文件
再次对同一目录调用脚本是安全的,并几乎立即返回结果:
$ split_directory.sh some/dir
moved 0 file(s)
最后,让我们来看一下特殊情况,即在生成的目录之一上调用脚本的情况:
$ time split_directory.sh some/dir/01
mkdir: created directory 'some/dir/01/01'
moved 1000 file(s)
real 0m19.265s
user 0m4.462s
sys 0m11.184s
$ time split_directory.sh some/dir/01
moved 0 file(s)
real 0m0.140s
user 0m0.015s
sys 0m0.123s
请注意,此测试是在一台相对较慢的老旧计算机上运行的。
祝你好运 :-)
*
扩展需要一些时间。也许可以先从更具针对性的文件名子集,如a*
开始,看看是否能在更合理的时间内返回结果。你还可以考虑使用 'find' 而不是 for 循环。此外,我担心在这个已经太大的目录中创建子目录。你有没有考虑在其他地方创建它们? - LinuxDisciplefind
命令的结果。 - Cole Tierneywhile read ... done
来逐个获取文件,从而将问题细分。 - laune*
在for
循环中扩展到250万个文件名本身并不是问题-实际上,for f in *; do ...
比ls | while read ...
要快得多。问题在于调用了250万次外部工具。 - mklement0for f in *
还是ls | while read
都不是解决方案的原因。最大命令行长度并没有起作用,因为*
的扩展结果并没有传递给 _外部实用程序_(并非该限制不适用于printf
等 _内置命令_,这正是我的答案所依赖的)。同意,一个 Shell 脚本也不是正确的解决方案。 - mklement0