使用目录名称作为模式递归重命名数百个文件(Rename recursively hundreds files using dir name as pattern)

编程入门 行业动态 更新时间:2024-10-16 00:19:31
使用目录名称作为模式递归重命名数百个文件(Rename recursively hundreds files using dir name as pattern)

目前的结构:

Cat and wolf ----- Volume 1.zip ----- Volume 2.zip Cat and fox ---- Volume 01.rar Rat and eagle ---- Rat and eagle 01.7x

如您所见,该结构不遵循简单的模式。 我希望它成为文件夹名称+号码 。 例如:

Cat and wolf 01.zip Cat and wolf 02.zip Cat and fox 01.rar Rat and eagle 01.7z

有什么办法可以达到这个结果吗?

Current structure:

Cat and wolf ----- Volume 1.zip ----- Volume 2.zip Cat and fox ---- Volume 01.rar Rat and eagle ---- Rat and eagle 01.7x

As you can see, the structure doesn't follow an easy pattern. I want it to become the folder name + number. e.g.:

Cat and wolf 01.zip Cat and wolf 02.zip Cat and fox 01.rar Rat and eagle 01.7z

There's any way to archieve this result?

最满意答案

这是一个非常具有挑战性的问题 最棘手的方面是递归的要求。 但是,正如您所指出的那样,命名模式并不简单。 为了构造目标文件名,我们必须从原始文件名stem解析出尾随数字,并将其连接到递归构造的目录名前缀上。

以下是我的解决方案,在bash中实现。 它迭代给定基目录中的所有对象。 对于子文件,它将它们移动到所需文件名下的目标目录中,而对于子目录,它会递归,建立在分隔符字符串上连接的目录名称的前缀。 初始基目录,目标目录和分隔符字符串都被参数化为函数参数。 为方便起见,我还在处理完基本目录后删除它,但前提是它只是在那时被发现是空的。

function renameSubFilesToDest { local base; local dest; local sep; local prefix; local file; local fileBase; local -i num; local ext; local new; local -i rc; ## parse arguments if [[ $# -lt 1 ]]; then echo 'too few arguments.' >&2; return 1; fi; if [[ $# -gt 4 ]]; then echo 'too many arguments.' >&2; return 1; fi; base="$1"; ## unconditionally take base dir as 1st arg ## take destination dir as optional 2nd arg, default to base if not given if [[ $# -ge 2 ]]; then dest="$2"; else dest="$base"; fi; ## take name separator as optional 3rd arg, default to space if not given if [[ $# -ge 3 ]]; then sep="$3"; else sep=' '; fi; ## take new name prefix as optional 4th arg, default to empty string if not given if [[ $# -ge 4 ]]; then prefix="$4"; else prefix=''; fi; ## iterate over all objects in the base dir for file in "$base"/*; do fileBase="$(basename -- "$file";)"; if [[ -d "$file" ]]; then ## recurse on dir, appending file basename and trailing sep to prefix renameSubFilesToDest "$file" "$dest" "$sep" "$prefix$fileBase$sep"; rc=$?; if [[ $rc -ne 0 ]]; then return 1; fi; else ## don't process files at the first depth level if [[ -z "$prefix" ]]; then continue; fi; ## parse num and ext from file name using bash extended regular expressions if [[ ! "$fileBase" =~ ([1-9][0-9]*)\.([^.]+)$ ]]; then echo "warning: file \"$fileBase\" does not match expected pattern." >&2; continue; fi; num=${BASH_REMATCH[1]}; ext=${BASH_REMATCH[2]}; ## derive the final file name in dest new="$dest/$prefix$(printf %02d $num).$ext"; printf '%s -> %s\n' "$file" "$new"; ## print status messages at run-time mv -i -- "$file" "$new"; rc=$?; if [[ $rc -ne 0 ]]; then echo "error: mv [$rc]." >&2; return 1; fi; fi; done; ## for convenience, remove the dir if it is now empty find "$base" -maxdepth 0 -empty -delete; return 0; } ## end renameSubFilesToDest()

这是即将到来的演示的一个小辅助函数,它只是在当前目录中设置输入文件结构:

function setupDemo { mkdir Cat\ and\ wolf Cat\ and\ fox Rat\ and\ eagle; touch Cat\ and\ wolf/Volume\ {1,2}.zip; touch Cat\ and\ fox/Volume\ 01.rar touch Rat\ and\ eagle/Rat\ and\ eagle\ 01.7z } ## end setupDemo()

这是输入文件结构上的函数演示:

setupDemo; ## set up the file structure find *; ## show it ## Cat and fox ## Cat and fox/Volume 01.rar ## Cat and wolf ## Cat and wolf/Volume 1.zip ## Cat and wolf/Volume 2.zip ## Rat and eagle ## Rat and eagle/Rat and eagle 01.7z renameSubFilesToDest .; ## run the solution ## ./Cat and fox/Volume 01.rar -> ./Cat and fox 01.rar ## ./Cat and wolf/Volume 1.zip -> ./Cat and wolf 01.zip ## ./Cat and wolf/Volume 2.zip -> ./Cat and wolf 02.zip ## ./Rat and eagle/Rat and eagle 01.7z -> ./Rat and eagle 01.7z find *; ## show the result ## Cat and fox 01.rar ## Cat and wolf 01.zip ## Cat and wolf 02.zip ## Rat and eagle 01.7z

正如GhostCat在他的评论中指出的那样,这个测试用例在技术上并不涵盖真正的递归文件结构,这需要至少两个级别的子目录。 我写了我的解决方案是完全递归的,因为你在标题中隐含了递归是目标,而且,无论如何,完全递归的解决方案会更通用,可能更有用。 这是一个二级演示,我们如何更改此演示的分隔符:

mkdir -p level\ one/level\ two; touch level\ one/level\ two/some\ file\ 7.txt; find *; ## level one ## level one/level two ## level one/level two/some file 7.txt renameSubFilesToDest . . _; ./level one/level two/some file 7.txt -> ./level one_level two_07.txt find *; ## level one_level two_07.txt

This was quite a challenging problem. The trickiest aspect was the requirement for recursion. But also, as you noted, the naming pattern is not straightforward. In order to construct the destination file name, we have to parse out the trailing number from the original file name stem and concatenate that onto the recursively-constructed prefix of directory names.

Below is my solution, implemented in bash. It iterates over all objects in the given base directory. For subfiles, it moves them into the destination directory under the required file name, while for subdirectories, it recurses, building up a prefix of directory names concatenated on a separator string. The initial base directory, destination directory, and separator string are all parameterized as function arguments. I also, for convenience, remove the base directory after it has been processed, but only if it is found to be empty at that time.

function renameSubFilesToDest { local base; local dest; local sep; local prefix; local file; local fileBase; local -i num; local ext; local new; local -i rc; ## parse arguments if [[ $# -lt 1 ]]; then echo 'too few arguments.' >&2; return 1; fi; if [[ $# -gt 4 ]]; then echo 'too many arguments.' >&2; return 1; fi; base="$1"; ## unconditionally take base dir as 1st arg ## take destination dir as optional 2nd arg, default to base if not given if [[ $# -ge 2 ]]; then dest="$2"; else dest="$base"; fi; ## take name separator as optional 3rd arg, default to space if not given if [[ $# -ge 3 ]]; then sep="$3"; else sep=' '; fi; ## take new name prefix as optional 4th arg, default to empty string if not given if [[ $# -ge 4 ]]; then prefix="$4"; else prefix=''; fi; ## iterate over all objects in the base dir for file in "$base"/*; do fileBase="$(basename -- "$file";)"; if [[ -d "$file" ]]; then ## recurse on dir, appending file basename and trailing sep to prefix renameSubFilesToDest "$file" "$dest" "$sep" "$prefix$fileBase$sep"; rc=$?; if [[ $rc -ne 0 ]]; then return 1; fi; else ## don't process files at the first depth level if [[ -z "$prefix" ]]; then continue; fi; ## parse num and ext from file name using bash extended regular expressions if [[ ! "$fileBase" =~ ([1-9][0-9]*)\.([^.]+)$ ]]; then echo "warning: file \"$fileBase\" does not match expected pattern." >&2; continue; fi; num=${BASH_REMATCH[1]}; ext=${BASH_REMATCH[2]}; ## derive the final file name in dest new="$dest/$prefix$(printf %02d $num).$ext"; printf '%s -> %s\n' "$file" "$new"; ## print status messages at run-time mv -i -- "$file" "$new"; rc=$?; if [[ $rc -ne 0 ]]; then echo "error: mv [$rc]." >&2; return 1; fi; fi; done; ## for convenience, remove the dir if it is now empty find "$base" -maxdepth 0 -empty -delete; return 0; } ## end renameSubFilesToDest()

Here's a little helper function for the upcoming demo, which simply sets up your input file structure in the current directory:

function setupDemo { mkdir Cat\ and\ wolf Cat\ and\ fox Rat\ and\ eagle; touch Cat\ and\ wolf/Volume\ {1,2}.zip; touch Cat\ and\ fox/Volume\ 01.rar touch Rat\ and\ eagle/Rat\ and\ eagle\ 01.7z } ## end setupDemo()

Here's a demo of the function on the input file structure:

setupDemo; ## set up the file structure find *; ## show it ## Cat and fox ## Cat and fox/Volume 01.rar ## Cat and wolf ## Cat and wolf/Volume 1.zip ## Cat and wolf/Volume 2.zip ## Rat and eagle ## Rat and eagle/Rat and eagle 01.7z renameSubFilesToDest .; ## run the solution ## ./Cat and fox/Volume 01.rar -> ./Cat and fox 01.rar ## ./Cat and wolf/Volume 1.zip -> ./Cat and wolf 01.zip ## ./Cat and wolf/Volume 2.zip -> ./Cat and wolf 02.zip ## ./Rat and eagle/Rat and eagle 01.7z -> ./Rat and eagle 01.7z find *; ## show the result ## Cat and fox 01.rar ## Cat and wolf 01.zip ## Cat and wolf 02.zip ## Rat and eagle 01.7z

As GhostCat pointed out in his comment, this test case does not technically cover a truly recursive file structure, which would require at least two levels of subdirectories. I wrote my solution to be fully recursive because you implied in your title that recursion was the goal, and, in any case, a fully recursive solution would be more generic and potentially more useful. Here's a demonstration with a second level, and how about we change the separator for this demonstration as well:

mkdir -p level\ one/level\ two; touch level\ one/level\ two/some\ file\ 7.txt; find *; ## level one ## level one/level two ## level one/level two/some file 7.txt renameSubFilesToDest . . _; ./level one/level two/some file 7.txt -> ./level one_level two_07.txt find *; ## level one_level two_07.txt

更多推荐

本文发布于:2023-08-04 06:00:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1410448.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:递归   百个   重命名   名称   模式

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!