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

11

我下载的很多文件名中都有垃圾邮件/垃圾信息,例如:

[ 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}"

我觉得参数扩展是两种方法中更糟糕的一种,但我喜欢它,因为在重命名后,我能够使用相同的变量对文件进行进一步处理(上面的代码用于在文件下载完成后为每个文件调用的脚本中)。

总之,我希望有比我更有经验的人能向我展示更好/更清晰的方法来完成上述操作,最好是以一种可以让我轻松重新分配旧/原始变量给新的/重命名的文件的方式。

谢谢


创建一个临时文件,使用ls命令更改它,然后应用结果(例如:mv“/Path/www.crap.com - file.name.ext”“/Path/file.name.ext”) - NeronLeVelu
1
请将ls命令的输出结果的一部分作为样本贴出(大约10行)。 - F. Hauri - Give Up GitHub
file.name.ext 部分有空格吗? - programmerjake
如果不是,则使用if [[ "${file_name}" =~ '(|.*/)[^/]*([^ /]+)$' ]]; then mv "${file_name}" "${BASH_REMATCH[1]}${BASH_REMATCH[2]}"; fi - programmerjake
5个回答

13

两种方法:使用重命名或仅使用纯粹的

由于有些人不喜欢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/A-Z/a-z/' *

更具方向性的样本

只需删除所有的空格方括号:

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

安全匹配stackoverflow问题的完整语法

有一种强大且安全的方法,使用rename工具:

由于这是常用工具,我们必须使用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命令:文件不会被覆盖,如果出现错误,您将收到错误消息。

使用和所谓的Bashisms重命名文件:

我更喜欢使用专用实用程序来完成此操作,但是即使使用纯粹的(也就是没有任何fork),也可以完成这个任务。

除了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的文件...
  • 分割文件名、计数器和扩展名,以制作带索引的newname
  • 循环,如果存在带有newname的文件
  • 最后重命名文件

7

利用以下经典模式:

 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函数的常见问题是在输出中包含隐藏目录的内容。例如,目录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脚本:

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声明为本地变量很有用,但在非常简单的脚本中可以省略。可以调整job_process函数以避免覆盖现有文件并报告有问题的项目。

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


你的 job_strategy 没有考虑路径名。 - user123444555621
这个s/^www\..*\.com - *//foo/www.bar.com上不起作用,因为有^。而且find总是会输出一个路径名,可能是./ - user123444555621
如果只有read需要新值来设置IFS,那么使用while IFS='|' read oldname newname; do可能比直接更改IFS更好。因为你的脚本会在修改后的IFS值下运行mv命令。 - Etan Reisner
这对于包含嵌入式换行符的文件名也很可能不安全。为此,您需要使用\0作为分隔符,并在选择和处理步骤上更加聪明。 - Etan Reisner
1
我通常将job_strategyjob_process合并到一个脚本中,因为在不使用卫生处理的情况下,传递文本数据是不可靠的。 - ivan_pozdeev
显示剩余2条评论

2
您可以使用 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. modifier:i(不区分大小写),g(全局替换)
大写/小写: 如果替换字符串的格式为/search_part/\c/modifier,则将文件名的选定部分(由正则表达式search_part确定)转换为小写,而在替换部分中使用大写的\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来撤消不需要的重命名操作。

P.S:我是这个工具的作者。


0
如果您想使用不依赖于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

0

如果您正在使用Ubuntu/Debian操作系统,请使用rename命令同时重命名多个文件。


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