【问题标题】:batch-renaming file name in a directory批量重命名目录中的文件名
【发布时间】:2011-12-01 03:07:48
【问题描述】:

我有一些文件的名称如下:

01_dpm_gsi_182.sl5
02_dpm_devel_gsi_182.sl5
03_DPM_DSI_181.sl5
04_globus_httpd_122.sl5
05_globus_httpd_client_cgi_132.sl5

我怎样才能重命名这些文件,以便得到类似的东西:

01_dpmgsi_s2011e01.sl5
02_dpmdevelgsi_s2011e02.sl5
....
....

最接近我的建议是这样的:

#!/usr/bin/perl -n

if (/^([^_]+)_(.+)_([^.]+)([.].+)$/) {
    my $s = $&;
    my $x = $1;
    my $y = $2;
    my $z = 2011;
    my $e = $4;

    $y =~ s/_//g;

    print "mv $s ${x}_${y}_s${z}e$x$e\n"
}

然后像这样使用它:

# ls | perl -n reName.pl > output
# bash ./output

有没有更好的方法或单线来做到这一点,可能使用 sed/awk?干杯!!

【问题讨论】:

    标签: perl bash sed awk


    【解决方案1】:

    不需要使用 bash 脚本来mv 文件,perl 可以做到这一点。下面的这个脚本使用File::Copy 来达到这个目的。我已经注释掉了执行移动的行,以便可以先用输入测试脚本。

    代码:

    use strict;
    use warnings;
    use v5.10;
    
    #use File::Copy;
    my $year = 2011;
    for (@ARGV) {
        my ($pre, @p) = split /_/;
        my $ext = pop @p;
        $ext =~ s/.*\./s${year}e$pre./;
        my $new = join '_', $pre, (join '', @p), $ext;
        say "old: $_";
        say "new: $new";
        say "------";
        #move $_, $new or die $!;
    }
    

    用法:

    perl script.pl *.sl5
    

    如果您没有 5.10 版,请将 say 与 print 交换并添加换行符。

    输出:

    old: 01_dpm_gsi_182.sl5
    new: 01_dpmgsi_s2011e01.sl5
    ------
    old: 02_dpm_devel_gsi_182.sl5
    new: 02_dpmdevelgsi_s2011e02.sl5
    ------
    old: 03_DPM_DSI_181.sl5
    new: 03_DPMDSI_s2011e03.sl5
    ------
    old: 04_globus_httpd_122.sl5
    new: 04_globushttpd_s2011e04.sl5
    ------
    old: 05_globus_httpd_client_cgi_132.sl5
    new: 05_globushttpdclientcgi_s2011e05.sl5
    

    【讨论】:

    • 简单易懂! +1
    【解决方案2】:

    这个 GNU sed 解决方案可能对您有用:

     sed 'h;s/[^_.]*\././;s/_//2g;s/^\(\([^_]*\)_[^.]*\)\./\1_s2011e\2e./;x;G;s/\(.*\)\n/mv -v \1 /' file
    

    解释:

    将原始文件名存储在保持空间 (HS) 中。删除文件扩展名之前的数字。删除除第一个 _ 之外的所有内容。使用分组、反向引用和添加的文本创建新文件名。交换到HS。附加新文件名。添加mv -v命令并删除嵌入的\n。

    正如已经说过的那样,文件名可能会让您感到困惑 - 当心!

    【讨论】:

      【解决方案3】:

      omg,永远不要解析 ls 的输出或构建由 shell 重新评估的查询。面对空白(或 $IFS 变量中的任何内容),解析文件列表(由ls 输出)是不可能正确的,因此在任何时候都是一个尴尬的解决方案。同样,构建正确转义的 shell 命令行几乎是不可能的。

      要继续使用 shell(尽管它是完成此任务的出色工具),请使用文件通配符,它​​可以正确拆分参数:

      for i in ./*
      do
          firstpart=${i%%_*}
          num=${firstpart#./}
          middlepart=${i#*_}
          middlepart=${middlepart%_*}
          middlepart=`printf %s "$middlepart" | tr -d _`
          lastpart=s2011e"$num".sl5
          echo mv "$i" "${firstpart}_${middlepart}_${lastpart}"
      done
      

      要实际执行,请删除最后一行中的echo。

      【讨论】:

      • @Jo So:+1 对ls 输出的评论,但在这种特殊情况下,它没有害处。我一直最喜欢的 shell 脚本的另一个 +1。
      【解决方案4】:

      您不需要单独的 bash 脚本来进行重命名。 Perl 有一个内置的rename 函数。有关文档,请参阅 http://perldoc.perl.org/functions/rename.html。

      我不会对正则表达式发表评论。也许可以改进它,但只要它工作正常,我认为没有必要改变它。

      【讨论】:

        【解决方案5】:

        这种事情has been covered before,答案是使用现有的重命名实用程序而不是自己编写。

        【讨论】:

        • OP 要求的文件名操作需要一个有点复杂的正则表达式。我认为 Perl 脚本在这里完全合适。
        • @Jonathan:这就是为什么您使用用 Perl 编写的rename,正如链接答案中提到的那样。
        【解决方案6】:

        好的,你要一个单行:

        ls |xargs -n1|awk -F'[_.]' '{idx=$1;nf=$1"_";for(i=2;i<NF-1;i++)nf=nf$i; nf=nf"_s2011e"idx"."$NF;print "mv "$0" "nf;}'
        

        这将为您打印所有mv 命令。

        如果目标名称都正确,您可以检查输出。如果是,您只需在行尾添加"|sh",它就会进行重命名。

        测试(不带|sh,查看输出)

        kent$  ls |xargs -n1|awk -F'[_.]' '{idx=$1;nf=$1"_";for(i=2;i<NF-1;i++)nf=nf$i; nf=nf"_s2011e"idx"."$NF;print "mv "$0" "nf;}'
        mv 01_dpm_gsi_182.sl5 01_dpmgsi_s2011e01.sl5
        mv 02_dpm_devel_gsi_182.sl5 02_dpmdevelgsi_s2011e02.sl5
        mv 03_DPM_DSI_181.sl5 03_DPMDSI_s2011e03.sl5
        mv 04_globus_httpd_122.sl5 04_globushttpd_s2011e04.sl5
        mv 05_globus_httpd_client_cgi_132.sl5 05_globushttpdclientcgi_s2011e05.sl5
        

        【讨论】:

        • 解析ls 输出以处理文件?这肯定会在空格、不寻常的文件名等上中断。这不是一个好习惯。
        • @Sorpigal 检查 OP 的示例文件请。和。 awk 之前的部分只是为了展示它是如何工作的。重点是 awk 部分,对吧?你当然可以使用 find、xargs 等来处理空格。但不是这个问题的重点。
        • @Kent 不能简单地使用 glob 吗?
        • glob 重命名?您是什么意思?从 OP 的 perl 脚本来看,扩展似乎是任何东西。 (我对 perl 了解不多)。同样,在我的回答中,“ls|xargs -n1”只是为了获取后面 awk 的输入。
        • @Kent:不管你的输入有多广为人知,不好的做法就是不好的做法。你应该说for file in * ; do awk ... "$file" ; done 或者至少是find . -maxdepth 1 -type f -print0 | xargs -0 ... 等等。ls | anything 没有任何借口,IMO。
        猜你喜欢
        • 2010-09-18
        • 1970-01-01
        • 2022-01-15
        • 2010-11-04
        • 2021-04-04
        • 1970-01-01
        • 2015-01-23
        • 1970-01-01
        • 2013-10-30
        相关资源
        最近更新 更多