我有一个包含多种文件的文件夹 如何通过文件扩展名对它们进行排序,留下一个名为“doc”的Word文档文件夹,一个带有jpgs的文件夹“jpg”等。
一些警告:
这些文件目前位于不同深度的子目录中。没有必要保持相对路径 - 未分类/ 1995 / summer / DCIM中的文件应该直接进入sorted / jpg。
两个文件可能具有相同的名称。在这种情况下,它必须忽略第二个文件[错误会很好],或者最好在移动时将某些内容附加到第二个文件的名称(file.jpg - > file_01.jpg)
由于我不知道所有文件类型,如果不存在具有该名称的文件,脚本应该创建该文件夹。 (即如果它命中文件“old.wpd”,它应该创建一个文件夹sorted / wpd。
我无法使用副本 - 我需要移动文件 - 因为我们正在处理大量文件,而且硬盘驱动器上没有空间来复制它们。
< / LI>我更喜欢Bash脚本,因为我对了解Bash有点兴趣,但如果需要可以使用Fish或ZSH。
为了我的学习,如果有一个可以保持相对路径的解决方案,我很想知道它是什么。
编辑:
我正在运行Mac,通过Brew安装coreutils
我失败的尝试使用了find,但我无法以可用的形式获得文件扩展名。
答案 0 :(得分:2)
这是您可以使用的一个可能的实用功能。它需要一个基本目录和一个文件路径,并将该文件移动到基本目录的相应子目录中的唯一命名文件。
对于生产用途,我建议扩展该功能以获取文件名列表而不是单个文件名。
无论如何,您可以使用-exec
命令的find
选项来安排在所有适当的文件上调用该实用程序。如果您按照建议扩展功能,则需要使用-exec ... +
而不是-exec ... {}
来触发它。 (有关详细信息,请参阅man find
。)
注意:我把它写成函数而不是脚本,但是find -exec
不能调用bash函数。因此,您需要将函数包装在脚本文件中,或者将其解包到脚本文件中。
重要提示:另外,我输入了这个;我没有验证它是否有效。与往常一样,只有在存在良好备份的情况下以及在受控环境中进行仔细测试后,才能对文件系统进行大量更改。
# Usage: ext_move <directory> <file>
ext_move() {
# Extract the filename from the path
local base=$(basename $2)
# Extract the (last) extension from the filename
local ext=${base##*.}
# Verify that it is really an extension
# This test could be much more rigorous (eg. only alphanumerics)
if [[ $ext = "$base" || $ext = "" ]]; then
echo "'$2': No extension; not moved"
return 1
fi
# Make sure the subdirectory exists
if ! mkdir -p "$1/$ext"
return 1
fi
# Try moving the file, but refuse to overwrite an existing file. If
# this fails, then we need to find a different file name
if ! mv -n "$2" "$1/$ext/$base" 2>/dev/null; then
# Strip the extension off the base:
base=${base%.$ext}
# We don't try *too* hard here, because the move might fail for other reasons.
local suf
for suf in _{01..99}; do
if mv -n "$2" "$1/$ext/$base$suf.$ext" 2>/dev/null; then
return
fi
done
# If we get here, we failed 100 different filenames. Maybe
# there is some other problem. (filesystem full, permissions, etc.)
# Repeat last move in order to present the error message
mv -n "$2" "$1/$ext/$base$suf.$ext"
fi
}
一些实施说明:
该函数旨在以原子方式工作,以防多个实例与不同的源文件并行执行,如果它是用xargs
而不是{{1}触发的情况。 }。所以它需要确保对目标文件名存在的测试是原子的,这排除了执行列表-exec
然后移动的事情。相反,我们只是尝试使用一种技术来进行移动,如果目标名称存在则该技术将失败。避免“修改前测试”竞争条件在脚本设计中始终很重要。
test -f $name
是一个Gnu扩展名,如果目标文件名存在,则导致移动失败。 Posix mv -n
只会覆盖文件,这显然不是我们想要的。如果我们没有Gnu mv
,我们可以通过使用mv
将新名称链接到旧文件来实现相同的效果;如果新名称存在,这将失败,满足锁定要求,但在这种情况下,我们仍然需要在链接成功后实际执行此操作。虽然代码稍微复杂一些,但它有一些优点:第一,它更便携,第二,它允许更好地检测错误条件。因此,它更适合生产脚本。
答案 1 :(得分:1)
这是一个简短的假设您已安装GNU coreutils。
#!/bin/bash
destination=~/Test/pwetpwet
find "$1" -type f -execdir bash -c '
base=${0#./}
extension=${base##*.}
[[ $extension != $base ]] || { echo >&2 "File $PWD/$base skipped: no extension"; exit 0; }
destdir=$1/${extension,,}
mkdir -p -- "$destdir" && mv --backup=numbered -- "$0" "$destdir"
' {} "$destination" \;
您可能希望回应“危险”行:
echo mkdir -p -- "$destdir" && echo mv --backup=numbered -- "$0" "$destdir"
用于测试目的。 --backup=numbered
的{{1}}扩展名将创建编号备份,而不是覆盖文件。
此脚本只接受一个参数(源文件夹);你可以很容易地使它适应两个参数(源和目标)。
我没有彻底测试过,所以请自行承担风险!
答案 2 :(得分:0)
#!/bin/bash
destination=/path/to/destination/folder
find . -type f -depth -print0 |
while read -d '' -r filename; do
base=$(basename "$filename")
extension=${base ##*.}
if [[ $base == $extension ]]; then
echo "ignoring file with no dot in the name: $filename"
continue
fi
# file.jpg and file.JPG should go to the same new folder
ext_dir="$destination/$( tr '[:upper:]' '[:lower:]' <<< "$extension")"
[[ -d "$ext_dir" ]] || mkdir "$ext_dir"
if [[ -f "$ext_dir/$base" ]]; then
# file.jpg already exists, find a new name
base_noext=${base%.*}
n=0
while ((n++)); do
printf -v base "%s_%03d.%s" "$base_noext" $n "$extension"
[[ -f "$ext_dir/$base" ]] || break
done
fi
if ln "$filename" "$ext_dir/$base"; then
echo "successfully linked: $filename -> $ext_dir/$base"
rm "$filename" || echo "could not remove: $filename"
else
echo "could not link: $filename -> $ext_dir/$base"
fi
done
使用硬链接(ln
)意味着您不必复制字节,因此假设您在同一文件系统中移动,这应该非常有效。