问题陈述:
我有多个目录,对应系列名称,每个目录下都有随机文件名,如下所示:
Title.S01E01/1234abc.mkv
Title.S01E02/5678def.mkv
Title.S01E03/10101qpr.mkv
....etc
每个目录要么是空的,要么最多包含一个文件。
我需要用目录名重命名每个目录下的文件,保留扩展名。
研究:
我尝试了通常的查找/重命名组合,但我无法准确地找出从找到的文件列表中提取目录名称所需的正则表达式,并将其适当地传递给重命名命令。
答案1
请阅读所有的在运行代码之前回答。
在等的父目录中运行此命令Title.S01E01/
:Title.S01E02/
find . -path '*/*/*' -type f -print -exec sh -c '
f="$1"
d="${f%/*}"
cd "$d" || exit 1
d="${d##*/}"
f="${f##*/}"
e="${f##*.}"
if [ "$e" = "$f" ]; then
e=""
else
e=".$e"
fi
mv -i -- "$f" "$d$e"
' find-sh {} \;
笔记:
这不符合
rename
标题中的“使用 Linux”的要求;rename
从未使用过。但代码还是可以完成工作。据我所知,代码是可移植的。继续阅读以了解一些不可移植的改进。
该解决方案按照图块的要求以递归方式工作。
-print
仅向您显示正在处理的文件。-path '*/*/*'
-mindepth 2
是GNU 的可移植(半)等效版本find
。重点是保留起始目录中的所有文件(如果有)。shell 代码不会检索保存当前处理文件的目录的真实名称;它会从起始find
目录的引用位置获取名称.
(因为我们这么说过find . …
:)。这不是您想要的文件名称。从技术上讲,只要在调用中将起始目录指定为或,第一个in
*
总是-path '*/*/*'
会匹配至少我们的.
.也会起作用。-path './*/*'
.
./
find
如果您需要保护起始目录子目录中的子目录中的文件,那么您需要
-maxdepth 2
GNUfind
或等效(ish)解决方案。穷人的便携式解决方案似乎是! -path '*/*/*/*'
;更好的便携式解决方案包括-prune
但事情变得复杂了。上述链接还详细说明了使用
*
with时可能出现的无效字符问题-path
。在 GNU 中find
绝对更喜欢-mindepth 2
(-maxdepth 2
如果需要)而不是-path …
。该shell代码主要操作字符串来获取目录名,文件名和“扩展名”。
我把“扩展”写在引号中,因为在 Linux 中,所谓的扩展只是名称的一部分。这种区别更多地存在于用户心中,而不是 Linux 文件系统或操作系统设计中(尽管它确实存在于 DOS/Windows 文件系统和操作系统设计中)。
支持没有“扩展名”的文件;即使目录名中有一个点,它们也受到支持(但期望文件从目录名中获得“扩展名”)。
隐藏文件(名称以点开头)可能会取消隐藏;取消隐藏的文件可能会被隐藏。这仅取决于相应目录的名称。代码不会尝试保留前导点或缺少前导点。
如果目标名称恰好是一个目录(即另一个更深的子目录),那么文件将被移动到其中,因为这是工作原理
mv
。您说每个目录“最多只有一个文件”。我的代码
mv -i
无论如何都会使用,因此如果您犯了一个无意的错误,额外的文件不会自动覆盖任何内容。如果您收到提示,请思考、调查、中止(Ctrl+ c)。第二次运行代码应该是安全的(例如在中止之后),唯一的副作用是来自已重命名文件
'something.foo' and 'something.foo' are the same file
的消息。除非是目录名称的mv
情况。例如,如果原始文件是(没有“扩展名”)并且第一次运行将其重命名为,则第二次运行将将其重命名为稳定名称。如果所有文件最初都以“扩展名”命名,那么您永远不会遇到这种怪癖。something.foo
bar
something.foo
something.foo.foo
find-sh
解释如下:中的第二个 sh 是什么sh -c 'some shell code' sh
?任何想要修改我的代码的人都不要忘记如何
find -exec sh -c
安全使用。