基于多种模式重命名文件的更好方法

我下载的很多文件都有垃圾文件,例如

[ www.crap.com ] file.name.ext

www.crap.com - file.name.ext

我提出了两种方法来处理它们,但是它们都显得很笨重:

参数扩展:

 if [[ ${base_name} != ${base_name//\[+([^\]])\]} ]] then mv -v "${dir_name}/${base_name}" "${dir_name}/${base_name//\[+([^\]])\]}" && base_name="${base_name//\[+([^\]])\]}" fi if [[ ${base_name} != ${base_name//www.*.com - /} ]] then mv -v "${dir_name}/${base_name}" "${dir_name}/${base_name//www.*.com - /}" && base_name="${base_name//www.*.com - /}" fi # more of these type of statements; one for each type of frequently-encountered pattern 

然后用echo / sed

 tmp=`echo "${base_name}" | sed -e 's/\[[^][]*\]//g' | sed -e 's/\s-\s//g'` mv "${base_name}" "{tmp}" 

我觉得参数扩展是更糟糕的两个,但我喜欢它,因为我能够保持相同的variables分配给文件进行进一步处理后重命名(上面的代码是在一个脚本中使用的每个文件文件下载完成后)。

所以无论如何,我希望有一个更好的/更清洁的方式来做到这一点,比我更有知识的人可以告诉我,最好以一种方式,让我很容易重新分配旧/原来的variables到新的/重命名的文件。

谢谢

两个答案:使用perl重命名或使用 bash

因为有些人不喜欢perl,所以只写了我的 bash版本

使用rename命令重命名文件。

介绍

是的,这是一个典型的rename命令,它是专为:

 man rename | sed -ne '/example/,/^[^ ]/p' For example, to rename all files matching "*.bak" to strip the extension, you might say rename 's/\.bak$//' *.bak To translate uppercase names to lower, you'd use rename 'y/AZ/az/' * 

更多面向样本

简单地删除所有空格方括号

 rename 's/[ \[\]]*//g;' *.ext 

重命名所有.jpg ,编号从1

 rename 's/^.*$/sprintf "IMG_%05d.JPG",++$./e' *.jpg 

演示:

 touch {a..e}.jpg ls -ltr total 0 -rw-r--r-- 1 user user 0 sep 6 16:35 e.jpg -rw-r--r-- 1 user user 0 sep 6 16:35 d.jpg -rw-r--r-- 1 user user 0 sep 6 16:35 c.jpg -rw-r--r-- 1 user user 0 sep 6 16:35 b.jpg -rw-r--r-- 1 user user 0 sep 6 16:35 a.jpg rename 's/^.*$/sprintf "IMG_%05d.JPG",++$./e' *.jpg ls -ltr total 0 -rw-r--r-- 1 user user 0 sep 6 16:35 IMG_00005.JPG -rw-r--r-- 1 user user 0 sep 6 16:35 IMG_00004.JPG -rw-r--r-- 1 user user 0 sep 6 16:35 IMG_00003.JPG -rw-r--r-- 1 user user 0 sep 6 16:35 IMG_00002.JPG -rw-r--r-- 1 user user 0 sep 6 16:35 IMG_00001.JPG 

以安全的方式匹配SO问题的完整语法

使用rename实用程序有一个强大而安全的方法:

由于这是perl常用工具,我们必须使用perl语法:

 rename 'my $o=$_; s/[ \[\]]+/-/g; s/-+/-/g; s/^-//g; s/-\(\..*\|\)$/$1/g; s/(.*[^\d])(|-(\d+))(\.[a-z0-9]{2,6})$/ my $i=$3; $i=0 unless $i; sprintf("%s-%d%s", $1, $i+1, $4) /eg while $o ne $_ && -f $_; ' * 

测试规则:

 touch '[ www.crap.com ] file.name.ext' 'www.crap.com - file.name.ext' ls -1 [ www.crap.com ] file.name.ext www.crap.com - file.name.ext rename 'my $o=$_; ... ... ...' * ls -1 www.crap.com-file.name-1.ext www.crap.com-file.name.ext touch '[ www.crap.com ] file.name.ext' 'www.crap.com - file.name.ext' ls -1 www.crap.com-file.name-1.ext [ www.crap.com ] file.name.ext www.crap.com - file.name.ext www.crap.com-file.name.ext rename 'my $o=$_; ... ... ...' * ls -1 www.crap.com-file.name-1.ext www.crap.com-file.name-2.ext www.crap.com-file.name-3.ext www.crap.com-file.name.ext 

… 等等…

…并且在不使用-f标志来rename命令时安全:文件不会被覆盖,如果出现错误,您将收到一条错误消息。

通过使用bash和所谓的bashisms重命名文件:

我更喜欢通过使用专用工具来实现这一点,但是这甚至可以通过使用纯粹的 bash (也就是没有任何分支)

没有任何其他的二进制比bash(没有sedawktr或其他)的使用:

 #!/bin/bash for file;do newname=${file//[ \]\[]/.} while [ "$newname" != "${newname#.}" ] ;do newname=${newname#.} done while [ "$newname" != "${newname//[.-][.-]/.}" ] ;do newname=${newname//[.-][.-]/-};done if [ "$file" != "$newname" ] ;then if [ -f $newname ] ;then ext=${newname##*.} basename=${newname%.$ext} partname=${basename%%-[0-9]} count=${basename#${partname}-} [ "$partname" = "$count" ] && count=0 while printf -v newname "%s-%d.%s" $partname $[++count] $ext && [ -f "$newname" ] ;do :;done fi mv "$file" $newname fi done 

以文件作为参数运行,例如:

 /path/to/my/script.sh \[* 
  • 用点替换空格和方括号
  • 替换.--.序列-.--或者只有一个-
  • 测试文件名是否不相同,没有任何关系。
  • 测试一个文件是否存在newname
  • 分割文件名,计数器和扩展名,用于建立索引的新名称
  • 如果文件与新名称存在,则循环
  • 最后重命名该文件。

利用以下经典模式:

  job_select /path/to/directory| job_strategy | job_process 

其中job_select负责选择作业的对象, job_strategy准备这些对象的处理计划, job_process最终执行计划。

这假定文件名不包含竖线| 也不是一个换行符。

job_select函数

  # job_select PATH # Produce the list of files to process job_select() { find "$1" -name 'www.*.com - *' -o -name '[*] - *' } 

find命令可以检查由文件系统维护的文件的所有属性,如创建时间,访问时间,修改时间。 也可以通过告诉find不下降到挂载的文件系统来控制文件系统的探测方式,允许多少递归级别。 将管道附加到find命令以基于文件名执行更复杂的选择是很常见的。

避免将隐藏目录的内容包含在job_select函数的输出中的job_select 。 例如,目录CVS.svn.svk.git被相应的源代码管理工具使用,并且在job_select函数的输出中包含它们的内容几乎总是错误的。 无意中批处理这些文件,可以很容易地使受影响的工作副本不可用。

job_strategy函数

 # job_strategy # Prepare a plan for renaming files job_strategy() { sed -e ' h s@/www\..*\.com - *@/@ s@/\[^]]* - *@/@ x G s/\n/|/ ' } 

这个命令读取job_select的输出并为我们的重命名作业制定计划。 该计划由具有由字符分隔的两个字段的文本行表示 ,第一个字段是文件的旧名称,第二个字段是文件的新计算文件,它看起来像

 [ www.crap.com ] file.name.1.ext|file.name.1.ext www.crap.com - file.name.2.ext|file.name.2.ext 

用来制定计划的特定程序本质上是无关紧要的,但是在例子中使用sed是很常见的。 awkperl为此。 让我们通过这里使用的sed script:

 h Replace the contents of the hold space with the contents of the pattern space. … Edit the contents of the pattern space. x Swap the contents of the pattern and hold spaces. G Append a newline character followed by the contents of the hold space to the pattern space. s/\n/|/ Replace the newline character in the pattern space by a vertical bar. 

使用多个过滤器来准备计划可能更容易。 另一个常见的情况是使用stat命令将创建时间添加到文件名。

job_process函数

 # job_process # Rename files according to a plan job_process() { local oldname local newname while IFS='|' read oldname newname; do mv "$oldname" "$newname" done } 

调整输入字段分隔符 IFS以使函数读取job_strategy的输出。 将oldnamenewname声明为local在大型程序中很有用,但可以在非常简单的脚本中省略。 可以调整job_process函数以避免覆盖现有文件并报告有问题的项目。

关于shell程序中的数据结构请注意,使用管道将数据从一个阶段传输到另一个阶段:学徒通常依靠变量来表示这样的信息,但事实证明这是一个笨拙的选择。 相反,最好将数据表示为表格文件,或将数据表示为从一个进程移动到另一个进程的表格数据流,在这种形式下,数据可以通过sedawkjoinpastesort等强大的工具轻松处理 – 仅用于引用最常见的。

如果您使用Ubunntu / Debian os use rename命令来重命名多个文件。

如果你想使用不依赖于perl的东西,你可以使用下面的代码(我们称之为sanitizeNames.sh )。 它只显示了一些情况,但是使用字符串替换tr(和sed)很容易扩展。

  #!/bin/bash ls $1 |while read f; do newfname=$(echo "$f" \ |tr -d '\[ ' \ # Removing opened square bracket |tr ' \]' '-' \ # Translating closing square bracket to dash |tr -s '-' \ # Squeezing multiple dashes |tr -s '.' \ # Squeezing multiple dots ) newfname=${newfname//-./.} if [ -f "$newfname" ]; then # Some string magic... extension=${newfname##*\.} basename=${newfname%\.*} basename=${basename%\-[1-9]*} lastNum=$[ $(ls $basename*|wc -l) ] mv "$f" "$basename-$lastNum.$extension" else mv "$f" "$newfname" fi done 

并使用它:

  $ touch '[ www.crap.com ] file.name.ext' 'www.crap.com - file.name.ext' '[ www.crap.com ] - file.name.ext' '[www.crap.com ].file.anothername.ext2' '[www.crap.com ].file.name.ext' $ ls -1 *crap* [ www.crap.com ] - file.name.ext [ www.crap.com ] file.name.ext [www.crap.com ].file.anothername.ext2 [www.crap.com ].file.name.ext www.crap.com - file.name.ext $ ./sanitizeNames.sh *crap* $ ls -1 *crap* www.crap.com-file.anothername.ext2 www.crap.com-file.name-1.ext www.crap.com-file.name-2.ext www.crap.com-file.name-3.ext www.crap.com-file.name.ext 

你可以使用rnm

 rnm -rs '/\[crap\]|\[spam\]//g' *.ext 

以上将从文件名中删除[crap][spam]

您可以通过终止它们传递多个正则表达式模式; 或者重载-rs选项。

 rnm -rs '/[\[\]]//g;/\s*\[crap\]//g' -rs '/crap2//' *.ext 

这个替换字符串的一般格式是/search_part/replace_part/modifier

  1. search_part :正则表达式来搜索。
  2. replace_part :要替换的字符串
  3. 修饰符 :我(不区分大小写),g(全局替换)

大写小写:

/search_part/\c/modifier格式的替换字符串会使选定部分的文件名(由正则表达式search_part )小写,而替换部分中的\C (大写\ C)将使其成为大写。

 rnm -rs '/[abcd]/\C/g' *.ext ## this will capitalize all a,b,c,d in the filenames 

如果你有很多需要处理的正则表达式模式,那么把这些模式放在一个文件中,并用-rs/f选项传递文件。

 rnm -rs/f /path/to/regex/pattern/file *.ext 

你可以在这里找到一些其他的例子。

注意:

  1. rnm使用PCRE2(修正的PCRE)正则表达式。
  2. 您可以通过运行rnm -u来撤销不需要的重命名操作

PS:我是这个工具的作者。