在Linux中使用正则表达式重命名文件

91

我有一组文件,命名方式如下:

Friends - 6x03 - Tow Ross' Denial.srt
Friends - 6x20 - Tow Mac and C.H.E.E.S.E..srt
Friends - 6x05 - Tow Joey's Porshe.srt

我想将它们重命名为以下形式

S06E03.srt
S06E20.srt
S06E05.srt

如何在 Linux 终端中完成工作? 我已经安装了 rename,但是我使用以下命令时出现错误:

rename -n 's/(\w+) - (\d{1})x(\d{2})*$/S0$2E$3\.srt/' *.srt

我在另一篇帖子中分享了我的解决方案:https://dev59.com/ZW445IYBdhLWcg3wkLPU#60969424。 - Antonio Petricca
10个回答

106

在星号前面你忘记了一个点:

rename -n 's/(\w+) - (\d{1})x(\d{2}).*$/S0$2E$3\.srt/' *.srt

在OpenSUSE、RedHat和Gentoo上,你需要使用Perl版本的rename这个答案展示了如何获取它。在Arch上,该软件包称为perl-rename


7
OpenSUSE、RedHat、Gentoo不支持在rename命令中使用正则表达式。 - maresmar
1
@mmrmartin:这里使用的重命名脚本是由Larry Wall编写的。它曾经在文件/usr/bin/rename中,但也许已经被重命名了(无意冒犯)?在Debian上,脚本名称现在是/usr/bin/file-rename - Thor
5
openSUSE 使用来自 util-linux 软件包的 rename 命令,我没有找到任何提供 file-renameprenameperl-rename 的软件包,对我来说唯一有效的解决方案是 使用 cpan 安装 - maresmar
@mmrmartin 在 RHEL 6 上也遇到了同样的问题,该系统也使用基于 util-linuxrename 命令。请参考 https://dev59.com/tWgt5IYBdhLWcg3wywdJ#48280659。 - Jonathan Komar
2012年至2023年的更新:在Debian 12 Bookworm中,该软件包被称为“rename”,一旦安装完成,调用Perl脚本的命令为“file-rename”。 - undefined

46

简单方法

find + perl + xargs + mv

xargs -n2 可以每行打印两个参数。当与 Perl 的 print $_ 结合使用时(用于先打印 $STDIN),它成为一个强大的重命名工具。

find . -type f | perl -pe 'print $_; s/input/output/' | xargs -d "\n" -n2 mv

perl -pe 'print $_; s/OldName/NewName/' | xargs -n2 的结果如下:

OldName1.ext    NewName1.ext
OldName2.ext    NewName2.ext
OldName3.ext    NewName3.ext
OldName4.ext    NewName4.ext

我在我的系统上没有Perl的rename命令可用。
如何工作?
1. find . -type f 输出文件路径(或文件名...您可以通过正则表达式控制要处理的内容!) 2. -p 打印通过正则表达式处理的文件路径,-e 执行内联脚本 3. print $_ 首先打印原始文件名(与 -p 无关) 4. -d "\n" 按换行符切割输入,而不是默认的空格字符 5. -n2 每行打印两个元素 6. mv 获取前一行的输入内容

高级方法

这是我首选的方法,因为它非常可靠。高级部分是在管道中的每个可执行文件的标准输出中使用空字节,并在后续可执行文件的标准输入中处理它们的复杂性增加。为什么要这样做?:这样可以避免文件名中的空格和换行符引起的问题。

假设我想将所有的“.txt”文件重命名为“.md”文件:

find . -type f -printf '%P\0' | perl -0 -l0 -pe 'print $_; s/(.*)\.txt/$1\.md/' | xargs -0 -n 2 mv

这里的魔法在于管道中的每个过程都支持空字节(0x00)作为分隔符,而不是空格或换行符。前面提到的第一种方法使用换行符作为分隔符。请注意,我尝试轻松支持find .而不使用子进程。在这里要小心(你可能想在运行正则表达式匹配或更糟的是破坏性命令如mv之前检查find的输出)。
工作原理(仅包括上述更改):
  1. find中:使用-printf '%P\0'仅打印文件名而不包括路径,后跟空字节。根据您的用例进行调整,无论是匹配文件名还是整个路径。
  2. perlxargs中:使用-0将标准输入的分隔符设置为空字节(而不是空格)
  3. perl中:使用-l0将标准输出的分隔符设置为空字节(八进制000)

5
对我来说,这是最好的答案——使用开箱即用的工具进行单行处理。 - Koikos
2
这删除了我所有的文件。幸运的是,我做了备份。 - exebook
9
最后一个命令应该改为xargs -d '\n' -n2 mv,否则xargs会将文件名中的空格视为分隔符,并导致错误或者将文件重命名为无意义的名称。-d '\n'参数指定换行符作为分隔符。GNU xargs具有-d参数,但对于那些没有该参数的实现(如我使用的FreeBSD),可以通过在输出中使用sed来转义所有空格并在管道传递给xargsfind . -type f | perl -pe 'print $_; s/input/output/' | sed 's/ /\\ /g' xargs -n2 mv。(也许不太优雅。) - s.co.tt
4
一种更好的将空格视为普通字符的方法是使用不同的分隔符。Xargs 支持0字节,find命令也支持。我会使用 find -print0 然后跟上 xargs -0 命令。 - Jonathan Komar
1
另一个改进的方法是通过grep预过滤查找结果,以最小化无操作重命名:find . -type f | grep 'input' | perl -pe 'print $_; s/input/output/' | xargs -n2 mv - beporter
显示剩余2条评论

16

使用mmv(批量移动?)

这很简单但很实用:通配符*匹配不包含/的任何字符串,而?匹配要匹配的字符串中的任何字符。 在替换字符串中,使用#N引用第N个通配符匹配。

在您的情况下:

mmv 'Friends - 6x?? - Tow *.srt' 'S06E#1#2.srt'

这里,#1#2 代表被 ?? 捕获的两个数字(匹配 #1 和 #2)。
因此正在进行以下替换:

The pattern string:     'Friends - 6x?? - Tow *           .srt'
matches this file:       Friends - 6x03 - Tow Ross' Denial.srt
                                     ↓↓
will be renamed to:             S06E03.srt

就我个人而言,我用它来填充数字,使得编号的文件在按字典顺序排序时以所需的顺序出现(例如:1.出现在10.之前):file_?.extfile_0#1.ext


mmv还提供了通过[]以及;进行匹配。

你不仅可以批量重命名,还可以批量移动、复制、追加链接文件。

详见手册页


12
编辑:发现了一种更好的方法来列出文件,而不使用IFSls,同时仍然符合sh标准。

我会写一个shell脚本来实现这个功能:


#!/bin/sh
for file in *.srt; do
  if [ -e "$file" ]; then
    newname=`echo "$file" | sed 's/^.*\([0-9]\+\)x\([0-9]\+\).*$/S0\1E\2.srt/'`
    mv "$file" "$newname"
  fi
done

之前的脚本:

#!/bin/sh
IFS='
'
for file in `ls -1 *.srt`; do
  newname=`echo "$file" | sed 's/^.*\([0-9]\+\)x\([0-9]\+\).*$/S0\1E\2.srt/'`
  mv "$file" "$newname"
done

在这个例子中,IFS='\n'代表什么?我喜欢它,因为它不使用任何特殊的东西。 - Sobvan
IFS:内部字段分隔符,用于扩展后的单词拆分和使用read内置命令将行拆分为单词。默认值为“<空格> <制表符> <换行符>”--(来自man bash)。 将其更改为\n可使每行获得一个文件。 - Creak
你可以扩展脚本以支持递归操作:for file in `find . -type f`; do(但是你需要更新sed以捕获路径) - Goran.it

10
并非所有的发行版都提供支持上文示例中所使用的正则表达式的rename实用程序 - 其中包括RedHat、Gentoo以及它们的衍生产品。
可以尝试使用的替代方案是perl-renamemmv

7
如果你的Linux系统没有提供rename命令,你也可以使用以下命令:
find . -type f -name "Friends*" -execdir bash -c 'mv "$1" "${1/\w+\s*-\s*(\d)x(\d+).*$/S0\1E\2.srt}"' _ {} \;

我经常使用这个片段在控制台中使用正则表达式进行替换。
我不太擅长Shell,但据我所知,这段代码的解释如下:您的find搜索结果将被传递到一个bash命令(bash -c)中,在其中,您的搜索结果将作为源文件存在于$1中。紧随其后的目标是在子shell中进行替换的结果,其中$1的内容(在此处为{1//find/replace}中的1参数替换内部)也将是您的搜索结果。 {}将其传递给-execdir的内容。
非常感谢更好的解释 :)
请注意:我只复制了您的正则表达式;请先使用示例文件测试它。根据您的系统,您可能需要将\d和\w更改为字符类,如[[:digit:]]或[[:alpha:]]。然而,\1应该对组起作用。

1
正如bash手册所说:“-c string 如果存在-c选项,则从字符串中读取命令。如果字符串后面有参数,则将它们分配给位置参数,从$0开始。”因此,您甚至可以改进您的命令:find . -type f -name“Friends *”-execdir bash -c' mv“$ 0”“ $ {0 / \ w + \ s * - \ s *(\ d)x(\ d +).* $ / S0 \ 1E \ 2.srt}”' {} \; - Louis Caron

6

使用regex-rename

它非常容易安装(与其他工具不同):

pip3 install regex-rename

使用以下命令进行重命名:

regex-rename "(\d{1})x(\d{2})" "S0\1E\2.srt" --rename

在实际重命名之前,首先尝试“干运行”模式(不使用--rename标记),以检查其是否看起来正常。它显示了每个组匹配的内容,因此您可以调试正则表达式,直到它完美无缺。
它需要两个参数:匹配器和替换模式,不要使用奇怪的s/.../.../语法。此外,我太懒了,只能与季节+剧集模式配合使用,而不能完全匹配。
我自己制作了它,因为我发现没有像这样的好工具。我很乐意听取您的反馈。

看起来非常流畅。希望能够通过查找传递文件,以便更好地控制/不要/进入目录等。 - Chris

6

我认为最简单也最通用的方法是使用 for 循环sedmv。首先,您可以在管道中检查您的正则表达式替换:

ls *.srt | sed -E 's/.* ([0-9])x([0-9]{2}) .*(\.srt)/S\1E\2\3/g'

如果它打印出正确的替换,只需将其放入一个带有mvfor循环中。
for i in $(ls *.srt); do 
    mv $i $(echo $i | sed -E 's/.* ([0-9])x([0-9]{2}) .*(\.srt)/S\1E\2\3/g') 
    done

2
你可以使用rnm:

你可以使用rnm

rnm -rs '/\w+\s*-\s*(\d)x(\d+).*$/S0\1E\2.srt/' *.srt

解释:

  1. -rs :替换字符串的形式为/search_regex/replace_part/modifier
  2. (\d)(\d+)(\d)x(\d+) 中是两个捕获组(分别为\1\2)。

更多示例请点击此处


运行得非常好,它还在执行任何操作之前显示了文件名的转换。 <3 - ssi-anik

1
如果您使用 rnr,则命令将是:
rnr -f '.*(\d{1})x(\d{2}).*' 'S0${1}E${2}.str' *.srt

rnr 有一个好处,就是能够 撤销 命令。


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