批量重命名文件

我有一些文件:
10.3.100.179_01_20161018_230014_5335.jpg
10.3.100.179_01_20161018_231514_0814.jpg
10.3.100.179_01_20161018_233014_5706.jpg
10.3.100.179_01_20161018_234514_0896.jpg
10.3.100.179_01_20161018_230114_5395.jpg
10.3.100.179_01_20161018_231614_1145.jpg
10.3.100.179_01_20161018_233114_6047.jpg
10.3.100.179_01_20161018_234614_0547.jpg
10.3.100.179_01_20161018_230114_5492.jpg
10.3.100.179_01_20161018_231614_1264.jpg
10.3.100.179_01_20161018_233114_6146.jpg
10.3.100.179_01_20161018_234614_0658.jpg
10.3.100.179_01_20161018_230214_5630.jpg
10.3.100.179_01_20161018_231714_7135.jpg
我想按照这个格式重新命名。
10.4.100.135_01_20161013131108389_TIMING.jpg
10.4.100.135_01_20161013131111390_TIMING.jpg
10.4.100.135_01_20161013131114401_TIMING.jpg
10.4.100.135_01_20161013131117431_TIMING.jpg
10.4.100.135_01_20161013131120418_TIMING.jpg
10.4.100.135_01_20161013131123461_TIMING.jpg
10.4.100.135_01_20161013131126511_TIMING.jpg
需要将时间戳中的下划线_移除,并添加_TIMING

2http://unix.stackexchange.com/questions/174387/mmv-mass-file-rename - sinclair
6请在你的问题中使用一致的示例。看起来你想要将 10.3.100.179 替换为 10.4.100.135。这是你想要的吗,还是你只想删除时间中的 _TIMING_ - terdon
4提醒大家,现在是时候告诉大家有一个适用于日期的iso8601标准(适用于文件名和日志条目)。所以也许你应该重新考虑更名为:YYYY-MM-DDThh:mm:ss.mmm(例如,你的第一个文件将变成:10.4.100.135_01_2016-10-13T13:11:08.389_TIMING.jpg(甚至可以删除"TIMING",因为现在它看起来明显像是一个日期和时间(以及毫秒),特别是当这个标准变得更加普遍时。T是标准的一部分,我习惯了它(去掉它会破坏标准^^)。 - Olivier Dulac
1@OlivierDulac 我不建议在文件名中使用:。我相信Windows不支持它。 - Justin
@Justin 说得好。标准建议在文件名中去掉它。我给了内部版本。 - Olivier Dulac
8个回答

安装renameutils并使用您喜欢的文本编辑器与qmv一起使用:
sudo apt install renameutils

qmv会加载编辑器中的所有名称,并在保存并关闭时将更改应用于实际文件。如果更改不一致(例如,两个文件获得相同的名称),它将中止操作而不触及任何内容。它还可以正确处理循环重命名。

通常我会这样做:

$ qmv -f do
为了只显示一个列的名称(仅限目的地),请按照以下方式进行设置:

qmw

如果你将它与SublimeText、Atom或Visual Studio Code的多光标结合起来使用,它会成为一个非常好用和强大的批量重命名工具。例如,对于Atom,你可以执行EDITOR="atom -w" qmv -f do

1欢迎来到askUbuntu!这太酷了! - αғsнιη
只是签了一下+!它。 ;) - J. Allan
2哇,好工具和好的GIF动画。非常棒的第一个回答,欢迎你! :) - Byte Commander
1感谢 @KasiyA 将图片嵌入,并感谢其他所有人的积极评论。 - ateijelo
qmv 在重命名文件时,是否可以发出警告,如果任何一次重命名导致文件被删除? - kasperd
1@kasperd是的,它会警告重命名计划包含错误,并打开一个交互式控制台,可以进行进一步操作。 - ateijelo
我从来没有意识到qmv支持其他方案格式,-f do看起来可能很有用。 - Peter Cordes
1对于Sublime Text,请使用 qmv --editor="sublime -w" -f do ./data/masks,然后使用 shift+alt 选择块并删除它。 - b-ak

使用rename...
rename -n 's/^([0-9]+\.[0-9]\.[0-9]+\.[0-9]+_[0-9]+_)([0-9]+)_([0-9]+)_([0-9]+)\.jpg/$1$2$3$4_TIMING\.jpg/' *
使用-n参数,这将输出它将要执行的操作,而不会进行任何更改。
rename(10.3.100.179_01_20161018_230014_5335.jpg, 10.3.100.179_01_201610182300145335_TIMING.jpg)
rename(10.3.100.179_01_20161018_231514_0814.jpg, 10.3.100.179_01_201610182315140814_TIMING.jpg)
rename(10.3.100.179_01_20161018_233014_5706.jpg, 10.3.100.179_01_201610182330145706_TIMING.jpg)
rename(10.3.100.179_01_20161018_234514_0896.jpg, 10.3.100.179_01_201610182345140896_TIMING.jpg)
如果看起来没问题,就把 -n 删掉。
$ rename 's/^([0-9]+\.[0-9]\.[0-9]+\.[0-9]+_[0-9]+_)([0-9]+)_([0-9]+)_([0-9]+)\.jpg/$1$2$3$4_TIMING\.jpg/' *
$ ls
10.3.100.179_01_201610182300145335_TIMING.jpg  10.3.100.179_01_201610182330145706_TIMING.jpg
10.3.100.179_01_201610182315140814_TIMING.jpg  10.3.100.179_01_201610182345140896_TIMING.jpg
解释... - `s/something/something_else/` 搜索和替换 - `^` 名称的开头(锚定) - `[0-9]` 任意数字 - `+` 前一个字符的一个或多个 - `\.` 字面上的 `. `(没有 `\`,这将匹配任何字符) - `()` 保留此部分 - `$1$2$3$3` 回引先前匹配并保留的内容 注意:命令末尾的 `*` 匹配当前目录中的所有可见文件。如有需要,请使用更合适的通配符。

2在某些发行版中,这个命令可以使用prename(p代表perl,因为第一个参数是一个perl表达式)来执行。 - Peter Cordes

mmv可以按照以下方式完成:

mmv '*_*_*_*_*.jpg' '#1_#2_#3#4#5_TIMING.jpg'

10.3.100.179_01_20161018_230014_5335.jpg 10.3.100.179_01_201610182300145335_TIMING.jpg

#1,#2,#3,... 分别对应此处的 '*'。

使用以下方式可以更加简短:

mmv '*_*_*.jpg' '#1#2#3_TIMING.jpg'

另一种重命名的方法:
$ rename -n 's/(.*)_(.*)_(.*)\./$1$2$3_TIMING./' *
10.3.100.179_01_20161018_230014_5335.jpg -> 10.3.100.179_01_201610182300145335_TIMING.jpg
10.3.100.179_01_20161018_230114_5395.jpg -> 10.3.100.179_01_201610182301145395_TIMING.jpg
10.3.100.179_01_20161018_230114_5492.jpg -> 10.3.100.179_01_201610182301145492_TIMING.jpg
10.3.100.179_01_20161018_230214_5630.jpg -> 10.3.100.179_01_201610182302145630_TIMING.jpg
10.3.100.179_01_20161018_231514_0814.jpg -> 10.3.100.179_01_201610182315140814_TIMING.jpg
10.3.100.179_01_20161018_231614_1145.jpg -> 10.3.100.179_01_201610182316141145_TIMING.jpg
10.3.100.179_01_20161018_231614_1264.jpg -> 10.3.100.179_01_201610182316141264_TIMING.jpg
10.3.100.179_01_20161018_231714_7135.jpg -> 10.3.100.179_01_201610182317147135_TIMING.jpg
10.3.100.179_01_20161018_233014_5706.jpg -> 10.3.100.179_01_201610182330145706_TIMING.jpg
10.3.100.179_01_20161018_233114_6047.jpg -> 10.3.100.179_01_201610182331146047_TIMING.jpg
10.3.100.179_01_20161018_233114_6146.jpg -> 10.3.100.179_01_201610182331146146_TIMING.jpg
10.3.100.179_01_20161018_234514_0896.jpg -> 10.3.100.179_01_201610182345140896_TIMING.jpg
10.3.100.179_01_20161018_234614_0547.jpg -> 10.3.100.179_01_201610182346140547_TIMING.jpg
10.3.100.179_01_20161018_234614_0658.jpg -> 10.3.100.179_01_201610182346140658_TIMING.jpg
如果这看起来符合你的要求,去掉-n

你也可以使用以下方法。首先备份您的文件,然后尝试这个方法:
find . -name "*.jpg" -type f -print0| while read -d $'\0' file
do
    #extension="${file##*.}"
    newfilename=$(echo "${file%.*}" | sed 's/\(.*\)_\(.*\)_/\1\2/')
    mv "$file" "$newfilename""_TIMING.jpg"
done

sed 's/\(.*\)_\(.*\)_/\1\2/') 删除时间戳中的_字符。

例如:

user@host$ ls -lart
total 8
drwxrwxr-x 6 user user 4096 Oct 21 10:21 ..
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_230014_5335.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_231514_0814.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_233014_5706.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_234514_0896.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_230114_5395.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_231614_1145.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_233114_6047.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_234614_0547.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_230114_5492.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_231614_1264.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_233114_6146.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_234614_0658.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_230214_5630.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_231714_7135.jpg
drwxrwxr-x 2 user user 4096 Oct 21 10:30 .

user@host$ find . -name "*.jpg" -type f -print0 | while read -d $'\0' file
> do
>  newfilename=$(echo "${file%.*}" | sed 's/\(.*\)_\(.*\)_/\1\2/')
>  mv $file $newfilename"_TIMING.jpg"
> done

10:35:20 t $ ls -lart
total 8
drwxrwxr-x 6 user user 4096 Oct 21 10:21 ..
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182300145335_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182315140814_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182330145706_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182345140896_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182301145395_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182316141145_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182331146047_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182346140547_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182301145492_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182316141264_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182331146146_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182346140658_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182302145630_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182317147135_TIMING.jpg
drwxrwxr-x 2 user user 4096 Oct 21 10:35 .

1这是最好的解决方案。当然,Zanna的解决方案对于一些文件来说是完美的,但由于通配符扩展是由shell执行的,他们的解决方案在大量文件上可能会失败(命令字符串过长)。 - rexkogitans
@rexkogitans:如果你使用的是现代Linux系统(例如任何版本的Ubuntu),命令行长度的限制可以达到几兆字节。如果这是一个问题,你可以使用find -maxdepth 1 -exec rename ... {} +将当前目录的列表批量添加到rename的命令行中(可以加上-name *.jpg或者其他你想要的条件)。这样执行速度比使用shell循环每个文件名都fork+exec sedmv要快得多,因为只需要进行一次重命名系统调用。(你可以使用bash内置的正则表达式功能来替代sed,但fork mv仍然相对较慢。) - Peter Cordes

你可能已经完成了,但是这里有一个(简单的)全bash解决方案:
“简单... bash解决方案”是矛盾修饰吗?
#!/bin/bash

#loop through all files ending in .jpg
for f in *.jpg;
do

    #cut out everything to the timestamp
    firsthalf=${f%_*_*_*}

    #get from the timestamp on
    lasthalf=${f#*_*_}

    #remove (all) underscores from timestamp
    #note the 2 forward slashes...
    lasthalf=${lasthalf//_/}

    #get our extension
    ext=${lasthalf##*.}

    #now we can remove the extension
    lasthalf=${lasthalf%.*}

    #rename the file
    #change `mv` to `echo` if you want to do a trial run first...
    mv "$f" "${firsthalf}_${lasthalf}_TIMING.${ext}"

done;

附言:循环中的逻辑已经通过了您提供的一个示例文件名的测试。


1最好使用bash正则表达式,例如[[ $f ~= (.*)_(.*)_(.*)_(.*)\.jpg ]];,然后用newname=${BASH_REMATCH[1]${BASH_REMATCH:2:4}TIMING.jpg或类似的方式来处理。(完全没有经过测试,可能是错误的,但是一般的思路是捕获组会放入BASH_REMATCH数组中。) - Peter Cordes
@PeterCordes:这是一个很好的观点!我从来没有使用过(bash)的捕获组,坦率地说,bash不是我首选尝试它们的语言。(我认为bash是一种丑陋的语言。)不过,这是一个很好的解决方案,它教会了我一些东西,所以赞赏你的贡献。 - J. Allan
是的,纯粹使用bash进行大量文本处理的主要原因是为了编写快速的选项补全功能。这给“丑陋代码”一词带来了全新的含义... - Peter Cordes
@PeterCordes:我明白你的观点,尽管不认为那段代码“丑陋”。在我看来,它可能不是你提供的两行解决方案,但并不难阅读;而且注释也写得很好... - J. Allan
我在谈论一般情况下的bash-completion代码是丑陋的(或者至少很难阅读)。实际上它并不真的丑陋,只是有点令人费解(例如,在bash中通过传递变量名来传递引用参数,并让被调用者使用printf -v "$3" ...来设置变量,其名称在$3中。)拆解整个命令行并重新组合起来非常难以理解和调试,至少在我尝试清理和修复错误时是这样的。请参考github上的代码 - Peter Cordes
格式和注释已经尽可能地好了,只是bash-completion确实需要跳过一些障碍。 - Peter Cordes
@PeterCordes:我觉得我们在谈论的不是同一个事情...我是不是错过了什么地方?(我看不出bash-completion如何适用于我的回答。) - J. Allan
这并不是我所说的“丑陋”的意思,而是指一般情况下对$BASH_REMATCH的使用。 - Peter Cordes
@PeterCordes: 啊哈哈... 现在我明白了。我的错。:0 - J. Allan

如果你可以使用图形用户界面(GUI),我会推荐使用pyRenamer。 它在大多数发行版中都有,比如Ubuntu。
sudo apt-get install pyrenamer
它可以做你想要的一切,甚至更多。 - 它可以使用模式,添加或抑制文本。 - 如果重命名照片,它可以访问EXIF数据,因此您可以根据日期/时间等生成模式。 - 重命名音乐文件时,可以使用一些元数据。 - 此外,它还有一个预览功能,可以防止一些难以恢复的错误。

给那位给我点踩的人...能解释一下为什么吗?楼主并没有明确说明是否可以使用图形用户界面(GUI)。我看到大多数答案都是关于控制台/脚本方式,但很多用户会喜欢有一个图形界面的解决方案。或者也许pyRenamer有一些我不知道的缺陷。无论如何,我想知道被点踩的原因。 - jrierab
我可以建议您使用Thunar批量重命名器,它具有快速交互式的图形用户界面方式。 - Tony Martin
@TonyMartin 这应该是在问题下面的评论,与答案无关 - Sergiy Kolodyazhnyy
这已经被废弃了,在官方仓库中找不到了。你可以尝试从源代码安装,即使可能有点棘手。 - Chris

这是您的原始内置find+xargs+sed+mv一行代码(喜欢一行代码):
find . -name "*.jpg" -print0 | sort -z | xargs -0 sh -c 'for filename; do mv "$filename" $(echo "${filename}" | sed "s/\([0-9]\{8\}\)_\([0-9]\{6\}\)_\([0-9]\{4\}\)/\1\2\3_TIMING/g"); done' sh

解释:
  • find . -name "*.jpg" | sort | xargs sh -c <command> sh:列出当前目录中的所有JPEG文件,然后对每个文件执行一个shell命令(排序是可选的,但如果你要在某个地方记录日志,保持一点整洁会更好)

  • -print0-z-0:在分词文件名时,用二进制0分隔项目是一个好习惯,以避免在中间出现空格引起问题(虽然这不是你的情况)

  • mv "$filename" $(echo "${filename}" | sed "s/\([0-9]\{8\}\)_\([0-9]\{6\}\)_\([0-9]\{4\}\)/\1\2\3_TIMING/g");:(sed正则表达式中的反斜杠并不利于其可读性,但它确实很简单)通过将由下划线分隔的8+6+4位数字序列替换为它们的连续串联加上这个_TIMING的东西来重命名每个文件(\i是对第i个正则表达式组的反向引用)。


参考资料:Xargs - man sed