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

时间:2013-12-17 08:30:38

标签: linux bash shell unix sed

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

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

我觉得参数扩展是两者中最差的,但我喜欢它,因为我能够保留分配给文件的相同变量,以便在重命名后进一步处理(上面的代码用于调用的脚本中)文件下载完成后的每个文件)。

所以无论如何我希望有一个更好/更清洁的方式来做上面的事情,比我自己更有知识的人可以告诉我,最好以一种方式让我轻松地将旧/原始变量重新分配给新的/重命名的文件。

由于

5 个答案:

答案 0 :(得分:7)

两个答案:使用重命名或使用 pure

由于有些人不喜欢perl,我写了我的 bash only version

使用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

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

使用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 重命名文件:

我更喜欢使用专用实用程序,但这甚至可以通过使用 (也就是没有任何分支)来完成

除了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 ,则
  • 循环
  • 最终重命名该文件。

答案 1 :(得分:6)

利用以下经典模式:

 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程序中的数据结构 注意使用管道将数据从一个阶段传输到另一个阶段:学徒通常依靠变量来表示这些信息,但事实证明这是一个笨拙的选择。相反,最好将数据表示为表格文件或表格数据流从一个流程移动到另一个流程,在这种形式下,数据可以通过强大的工具轻松处理,如sedawk,{ {1}},joinpaste - 仅引用最常见的内容。

答案 2 :(得分:1)

您可以使用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. 的字符串
  4. 修饰符:i(不区分大小写),g(全局替换)
  5. <强>大写/小写:

    /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
    

    您可以找到其他一些示例here

    注意:

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

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

答案 3 :(得分:0)

如果您使用的是Ubuntu / Debian,请使用rename命令重命名多个文件。

答案 4 :(得分: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