在目录中批量重命名文件名

3

我有一些文件的名称像这样:

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?干杯!
6个回答

6

不需要使用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版本,请使用print替换say并添加一个新行。

输出:

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 - jaypal singh

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

正如已经提到的,文件名可能会使您遇到麻烦-请注意!


2

哦我的天,从不要解析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: 对于ls输出的评论表示认同,但在这种特殊情况下没有害处。我的最爱shell脚本再加一分。 - MacUsers

1

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

我不会对正则表达式发表评论。它可能有改进的空间,但只要它能正确工作,我认为没有必要改变它。


1

1
文件名操作所需的正则表达式有些复杂。我认为在这里完全适合使用Perl脚本。 - Jonathan
@Jonathan:这就是为什么你要使用一个用Perl编写的rename,就像链接答案中提到的那样。 - ninjalj

0

好的,你要求一行代码:

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
@Sorpigal请检查OP的示例文件。而且,awk之前的部分只是为了展示它是如何工作的。重点是awk部分,对吧?当然你可以使用find、xargs等来处理空格。但这不是这个问题的重点。 - Kent
@Kent,直接使用glob不就可以了吗? - TLP
全局变量重命名?你的意思是什么?从 OP 的 Perl 脚本中,扩展名似乎可以是任何东西。(我不太了解 Perl)。再次说明,在我的答案中,“ls|xargs -n1” 只是为了获取后面 awk 的输入。 - Kent
我对awk不是很了解。=)但我认为除了通过管道输入到stdin之外,可能还可以以某种方式直接传递参数给awk。awk ... * - TLP
1
@Kent:无论你的输入有多么出名,不良实践都是不好的。你应该只说for file in *; do awk ... "$file"; done或者至少是find . -maxdepth 1 -type f -print0 | xargs -0 ...等等。在我看来,使用ls | anything是没有借口的。 - sorpigal

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