使用shell脚本替换文件名中的特定模式以重命名多个文件

239

编写一个简单的脚本,可以自动重命名多个文件。例如,我们想将文件*001.jpg重命名为用户定义的字符串+001.jpg(例如:MyVacation20110725_001.jpg)。此脚本的用途是使数码相机照片具有一些有意义的文件名。

我需要编写一个Shell脚本来实现这个功能。有人能建议一下如何开始吗?


我建议阅读 mv 的 man 手册。 - Jacob
相关问题:基于多个模式重命名文件的更好方法 - Michaël Le Barbier
好的线程:https://unix.stackexchange.com/questions/116753/command-to-mass-rename-files-according-to-pattern - St3an
重命名 's/.+(\d{3}:?).jpg/string$1.jpg/g' *.jpg - Ru5
8个回答

412

一个例子可以帮助你入门。

for f in *.jpg; do mv "$f" "$(echo "$f" | sed s/IMG/VACATION/)"; done

在这个例子中,我假设所有的图像文件都包含字符串IMG,你想用VACATION来替换它。

Shell会自动将*.jpg展开成所有匹配的文件。

mv的第二个参数(文件的新名称)是sed命令替换IMGVACATION的输出。

如果您的文件名包含空格,请注意"$f"的表示法。您需要使用双引号来保留空格。


10
使用 Bash,您还可以从字符串重定向:sed 's/foo/bar/' <<< "$f"。意思是使用 sed 命令从一个变量 $f 中提取的字符串,并对其进行替换操作,将字符串中的 "foo" 替换为 "bar"。重定向操作符 <<< 用于将该字符串作为输入传递给 sed。 - glenn jackman
2
非常好的例子Susam - 这个用法在MingW中对我也起作用,可以将名为*-1.0-SNAPSHOT.war的文件更改为*.war,像这样:for i in *.war; do mv ${i} \echo ${i} | sed 's/-1.0-SNAPSHOT.war/.war/'`; done` - mikequentel
只是为了补充一下..你也可以使用通配符,比如说有一堆文件名都是IMGnnnn.jpg,其中n代表一串数字;那么你可以执行类似这样的命令 s/IMG.*/VACATION\.jpg/。 - Ahdee
2
@MikeStewart,你提供的GNU文档链接中包含许多非POSIX功能。请参阅POSIX.1-2008:§ 2.6.2:参数扩展以获取有关参数扩展的POSIX规范。在您提供的另一个答案中${file//IMG/myVacation}替换不符合POSIX标准。它无法与dash一起使用。在Debian上,默认的shdash。当dash遇到${file//IMG/myVacation}时,会因为此语法不符合POSIX而失败并显示“Bad substitution”错误。 - Susam Pal
@SusamPal 哦,是的,我知道BASH有许多不符合POSIX标准的特性(手册上也有说明)。但我很高兴你澄清了并分享了一个更合适的示例链接。谢谢! - Mike Stewart
显示剩余3条评论

210
你可以使用rename实用程序按照模式重命名多个文件。例如,以下命令将在所有具有jpg扩展名的文件前添加字符串MyVacation2011_。
rename 's/^/MyVacation2011_/g' *.jpg
或者
rename <pattern> <replacement> <file-list>

但是这些命令可能有不同的变体,重命名就像在我的RedHat 6.5上使用mv命令一样。 - ixe013
1
这对我没用,因为我有太多的文件:-bash:/usr/bin/rename:参数列表太长。但我同意如果你有一个较小的列表会更好。 - TM.
11
@ixe013 这个重命名是 Perl 版本,不是内置实用程序。请按照 https://dev59.com/sGEh5IYBdhLWcg3wTB1c 的指南进行安装。 - 林果皞
2
“参数列表过长”问题可以通过使用find来规避:find . -name *.jpg -exec rename <pattern> <replacement> {} \;这将为每个文件调用一次重命名。 - Erik Forsberg
1
@CpILL 它使用正则表达式捕获组,并且替换字符串将用匹配和 $<groupNumber> 进行替换。例如:rename 's/([^-]+)-([^.]+)/$2-$1/g' *。模式 ^([^-]+)-([^.]+) 的意思是:从名称开头,捕获一个或多个不是 - 的字符,然后期望一个破折号,然后捕获一个或多个不是 . 的字符。$1 是第一个捕获,$2 是第二个。 - Eric Haynes
显示剩余3条评论

75
在这个例子中,我假设你的所有图像文件都以"IMG"开头,并且你想用"VACATION"替换它。
解决方案:首先识别所有jpg文件,然后替换关键字。
find . -name '*jpg' -exec bash -c 'echo mv $0 ${0/IMG/VACATION}' {} \; 

5
最佳答案是它允许遍历目录树,使用内置的bash工具,并且易于配置。此外,该示例提供了一种非破坏性测试,以确保更改正确无误。 - Dan Mergens
14
这里只是一个注释,非常明显但值得强调:'echo mv $0 ${0/IMG/VACATION}' 这部分是针对每个找到的文件实际运行的命令。因此,如果你保留 echo,它只会回显将要执行的操作。删除 echo 命令以实际移动文件,例如 find . -name '*jpg' -exec bash -c 'mv $0 ${0/IMG/VACATION}' {} \; - dmmd
2
赞赏您包含了 echo,这样用户就知道它是否有效。 - Tung
最有用的;尽管你可能应该添加@dmmd的注释。我实际上将echo的输出转储到一个shell脚本中进行检查,然后执行它...因为我很多疑。 - Brian

49
for file in *.jpg ; do mv $file ${file//IMG/myVacation} ; done

假设所有的图像文件都包含字符串"IMG",您希望用"myVacation"替换"IMG"。

使用bash,您可以直接使用参数扩展来转换字符串。

例如:如果文件是IMG_327.jpg,则mv命令将被执行,就像您执行mv IMG_327.jpg myVacation_327.jpg一样。并且这将针对在目录中找到的每个与*.jpg匹配的文件执行。

IMG_001.jpg -> myVacation_001.jpg
IMG_002.jpg -> myVacation_002.jpg
IMG_1023.jpg -> myVacation_1023.jpg
以此类推...


2
@MikeStewart,你提供的GNU文档链接中包含许多非POSIX功能。请参阅POSIX.1-2008:§ 2.6.2:参数扩展以获取有关参数扩展的POSIX规范。此答案中的${file//IMG/myVacation}替换不符合POSIX。它无法与dash一起使用。在Debian上,默认的shdash。当dash遇到${file//IMG/myVacation}时,会因为此替换语法不符合POSIX而失败,并显示“Bad substitution”错误。 - Susam Pal

12
find . -type f | 
sed -n "s/\(.*\)factory\.py$/& \1service\.py/p" | 
xargs -p -n 2 mv

eg 将当前工作目录中所有以“factory.py”结尾的文件重命名为以“service.py”结尾的名称。

解释:

  1. 在sed命令中,-n标志将抑制正常行为,在s///命令应用后不会回显输入到输出,s///上的p选项将强制写入输出,如果进行了替换。由于替换只会在匹配时进行,因此sed只会对以“factory.py”结尾的文件进行输出。

  2. 在s///替换字符串中,我们使用“&amp; ”将整个匹配字符串插入到替换中,后跟一个空格字符。由于这一点,我们的RE必须匹配整个文件名。在空格字符之后,我们使用“\1service.py”插入我们之前捕获的字符串“factory.py”之前的字符串,后跟“service.py”进行替换。因此,对于更复杂的转换,您将需要更改s///的参数(仍然与re匹配整个文件名)

示例输出:

foo_factory.py foo_service.py
bar_factory.py bar_service.py
  1. 我们使用xargs-n 2,每次处理sed输出的两个分隔字符串,并将它们传递给mv(我还加入了-p选项,以便在运行此命令时更安全)。大功告成。

注意:如果您面对更复杂的文件和文件夹情况,请参考此文章详细介绍了find(及其一些替代方法)


在我看来,这是唯一有效的解决方案。它美观、快速且灵活。 - Konstantin
1
最佳、最灵活的解决方案。特别是在环境(如 cygwin)中,其中某些解决方案所需的启用正则表达式的 replaceperl replace 不可用或难以获得。 - Domi

4
另一个选择是:
for i in *001.jpg
do
  echo "mv $i yourstring${i#*001.jpg}"
done

在正确使用之后,删除echo

使用#进行参数替换时,只保留最后一部分,所以您可以更改其名称。


这种方法不允许递归文件和文件夹结构,并具有其他几个缺陷。John提供的此解决方案取代了此方法,除了最简单的情况外。 - Domi
对我之前的评论做一个小补充:现在glob支持使用globstar选项进行递归。(但是该解决方案仍缺少正则表达式替换支持)。 - Domi

2

我不能对Susam Pal的答案进行评论,但如果你正在处理空格,我建议用引号括起来:

for f in *.jpg; do mv "$f" "`echo $f | sed s/\ /\-/g`"; done;

1
你可以尝试这个:
for file in *.jpg;
do
  mv $file $somestring_${file:((-7))}
done

你可以在man bash中查看"参数扩展"以更好地理解上述内容。

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