递归批量重命名

3

我有几个文件夹里面有一些文件,我想要对其重命名

Foo'Bar - Title

to

Title

我正在使用OS X 10.7。我查看了其他解决方案,但没有一个很好地解决了递归的问题。有什么建议吗?
4个回答

10
你的问题有两个部分:递归查找文件,以及重命名它们。对于第一个问题,如果每个目录都是当前目录的下一级,你可以列出当前目录中每个目录的内容(就像上面Mattias Wadman的答案一样)。但更通用的方法是使用find命令,也更容易理解。对于第二个问题,你可以使用sed,并弄清如何正确引用和传输数据(你应该最终了解这些),但使用rename命令会更简单。不幸的是,在Mac上没有内置此命令,但你可以通过Homebrew安装它,或者只需下载perl脚本并运行sudo install -m755 rename /usr/local/bin/rename进行安装。因此,你可以做到这一点:
find . -exec rename 's|[^/]* - ||' {} +

如果你想进行“干跑”以确保正确性,在重命名时请添加“-n”标志:
find . -exec rename -n 's|[^/]* - ||' {} +

为了理解它的工作原理,您真的应该阅读find的教程和rename的手册,但是我们可以将其分解如下:
  • find . 表示 "递归查找当前目录下的所有文件"。
  • 您可以添加其他测试以过滤内容(例如,-type f 如果您想跳过除常规文件外的所有内容,或者 -name '*标题' 如果您只想更改以“标题”结尾的文件),但这对于您的使用并非必要。
  • -exec+ 表示批处理找到的文件,并尽可能多地将它们代替任何出现在 '…' 中的命令中的 {}
  • rename 's|[^/]* - ||' {} 表示对于{}中的每个文件, 应用perl表达式 s|[^/]* - ||到文件名,如果结果不同,则将其重命名为该结果。
  • s|[^/]* - ||表示匹配正则表达式'[^/]* -'并将匹配项替换为''(空字符串)。
  • [^/]* -表示匹配以“ - ”结尾的任何非斜杠字符字符串。 因此,在 './A/FooBar - Title'中,它将匹配'FooBar -'。
我应该提到,当我像这样有一些复杂的事情要做时,如果在几分钟内并尝试使用find/sed/awk/rename等获取正确结果后仍无法做到,我通常会用Python和os.walk命令来编写代码。 如果您知道Python,那么这可能更容易理解(虽然更冗长且不太简单),并且更易于修改为其他用例,因此如果您感兴趣,请询问。

是的,正如我在另一条评论中所说的那样,rename 至少有三种不同的实现方式,其中至少有一种(令人尴尬的是,我实际上链接并推荐的那个……)会像我最初编写的那样做错事情。因此,我修改了正则表达式,只匹配斜杠后面的所有内容。 - abarnert
不错。我应该经常使用 find -exec,但我倾向于经常使用 while read 模式,因为它似乎更容易执行多个命令等操作。 - Mattias Wadman
您还可以按名称过滤要替换的文件集。例如,我需要递归重命名所有*.java文件为*.txt,因此我这样做:find . -name "*.java" -exec rename 's|\.java|\.txt|' {} + - Noam Ben Ari
你要如何查找忽略大小写的字符串,或者查找多个字符串(例如查找所有foo、FOO、Foo等等)?我的正则表达式将是/(foo|bar)/i。 - Nico
啊,我找到了(只需添加另一个|和i):find . -exec rename -v 's|foo|Foo|i' {} + - Nico
显示剩余2条评论

3

试试这个:

ls -1 * | while read f ; do mv "$f" "`echo $f | sed 's/^.* - //'`" ; done

我建议在运行命令之前,在mv之前添加一个echo,以确保命令看起来正确。正如abarnert在评论中指出的那样,此命令每次只能处理一个目录。
各个命令的详细解释: ls -1 *将为当前目录中的每个文件(和目录)输出一行(除了.文件)。因此,它将扩展为ls -1 file1 file2 ...-1告诉ls仅列出文件名,并且每行一个文件。
然后将输出导入到while read f ; ... ; done中,它将循环,直到read f返回零,直到达到文件结束。 read f从标准输入读取一行(在这种情况下是来自ls -1 ...的输出),并将其存储在指定的变量中,即f
在while循环中,我们使用两个参数运行mv命令,第一个参数是源文件"$f"(请注意引号以处理带有空格等的文件名),第二个参数是目标文件名,它使用sed和`(反引号)执行所谓的命令替换,将调用括号内的命令,并用标准输出替换它。 echo $f | sed 's/^.* - //'将当前文件$f导入sed,它将匹配正则表达式并进行替换(在s/中的s),并在标准输出上输出结果。正则表达式是^.* -,它将从字符串开头^(称为锚定)匹配任何字符.*,然后是-并将其替换为空字符串(//之间的字符串)。

谢谢!你介意解释一下它是做什么的吗?我想学习这个。 - George K.
这并不是用户想要的。例如:'mkdir A B ; touch "A/FooBar - Title" "B/FooBar - Title"; ls -1 * | while read f ; do mv "$f" "echo $f | sed 's/^.* - //'" ; done 只会输出6个错误。 - abarnert
但是我猜如果你真的想要递归地执行并且只包括文件等,则应该使用find而不是ls - Mattias Wadman
看一下 ls -1 返回的内容。它不是 "A/FooBar - Title" "B/FooBar - Title",而是 "" "A:" "FooBar - Title" "" "B:" "FooBar - Title"。你可以使用 ls */*Title,这在这里可以工作。但是,一旦匹配正确的文件,你的 mv 将把所有文件移动到 Title(而不是 A/Title 等),我想你不想要这样。 - abarnert
1
啊,你说得对,sed 命令会删除目录,这很糟糕。使用 s/^.* - // 正则表达式时,rename 会有同样的问题吗? - Mattias Wadman
显示剩余3条评论

2

我知道你想批量重命名,但我建议你使用Automator

它可以完美地工作,如果你将其创建为一个服务,你将在上下文菜单中看到该选项 :)


1

经过一些尝试和错误,我找到了这个解决方案,它对于解决相同的问题对我很有效。

find <dir> -name *.<oldExt> -exec rename -S .<oldExt> .<newExt> {} \;

基本上,我利用查找和重命名工具。这里的诀窍是找出在重命名中放置'{}'的位置(它代表需要由重命名处理的文件)。
附注:重命名不是内置的Linux实用程序。我使用OS X并使用homebrew安装了rename。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接