基于文件名使用Bash和Perl进行批量重命名

5
我可以帮您进行翻译。需要批量重命名当前目录下的文件,并从文件名末尾删除特定字符串。
示例:
foo-bar-(ab-4529111094).txt
foo-bar-foo-bar-(ab-189534).txt
foo-bar-foo-bar-bar-(ab-24937932201).txt

输出应该如下所示:
foo-bar.txt
foo-bar-foo-bar.txt
foo-bar-foo-bar-bar.txt

我希望删除每个文件名末尾的字符串-(ab-2492201),其中数字长度可能不同。首选Perl正则表达式而非模块,并且不使用任何工具和Bash oneliner命令高度优先。如何在Linux上使用Perl和Bash Shell实现?我对这两种解决方案都感兴趣。

你为什么不想使用任何Perl模块? - Borodin
在将代码插入到我的主脚本中后,使其可在多台机器上移植,这样不需要每台机器都安装模块依赖项。 - SilverShadow
有很多 Perl 的“核心”模块应该在任何标准的 Perl 安装中都可用。其中之一是 File::Find,但似乎它不适用于您的问题。 - Borodin
7个回答

7

尝试:

$ rename 's/-\(ab-\d+\)(?=\.txt$)//' *.txt

有一个用Perl编写的rename命令,它的第一个参数是描述如何转换文件名的Perl代码。您可以在自己的Perl程序或一行代码中使用相同的s///命令。

如果这样不起作用,请尝试使用prename而不是rename; 一些系统上安装了不同的、非基于Perl的rename命令,在这种情况下,Perl的命令可能被称为prename


奇怪。我刚刚创建了与您问题中完全相同的文件,然后粘贴了上面的“rename”命令,它可以正常工作。当您尝试时发生了什么?是否有任何错误消息?如果在“rename”命令中加入“-v”选项,是否会输出任何内容? - Smylers
2
显然有不止一个名为“rename”的命令;一些Linux发行版带有一个与Perl版本不同的非Perl版本。请参见:https://dev59.com/sGEh5IYBdhLWcg3wTB1c - Arnon Weinberg
Arnon Weinberg:好观点,谢谢。回答已更新以反映这一点。 - Smylers

5

使用Perl正则表达式重命名文件

通过findperlxargs,你可以使用下面这个一行代码进行操作

find . -type f | perl -pe 'print $_; s/input/output/' | xargs -n2 mv

没有调用 mv 的结果应该是
OldName NewName
OldName NewName
OldName NewName

它是如何工作的?

  1. find . -type f 输出文件路径(或文件名...可以通过正则表达式控制要处理的内容!)
  2. -p 打印要由正则表达式处理的文件路径,-e 执行内联脚本
  3. print $_ 首先打印原始文件名(与 -p 无关)
  4. -n2 每行打印两个元素
  5. mv 获取前一行的输入

4
在bash中,你可以这样写:
for file in *-\(ab-[0-9]*\)*; do
    newfile="${file/-(ab-[0-9]*)/}"
    mv "$file" "$newfile"
done

完美地按照我的意愿工作,而其他解决方案却没有,节省了我的时间,非常感谢! - SilverShadow

2

当你说“当前目录下”时,是指在当前目录中还是在当前目录及其子目录中的任何位置?

File::Find是实现后者的简单方法,它是一个核心模块,不需要安装。操作如下:

use strict;
use warnings;

use autodie;

use File::Find;

find(\&rename, '.');

sub rename {
  return unless -f;
  my $newname = $_;
  return unless $newname =~ s/-\(ab-[0-9]+\)(\.txt)$/$1/i;
  print "rename $_, $newname\n";
}

更新

该程序将仅在当前目录中更改所有具有给定文件名模式的文件名称。

请注意,初始的open循环仅用于创建重命名示例文件。

use strict;
use warnings;

use autodie;

open my $fh, '>', $_ for qw(
  foo-bar-(ab-4529111094).txt
  foo-bar-foo-bar-(ab-189534).txt
  foo-bar-foo-bar-bar-(ab-24937932201).txt
);

for (glob '*.txt') {
  next unless -f;
  my $newname = $_;
  next unless $newname =~ s/-\(ab-[0-9]+\)(\.txt)$/$1/i;
  print "rename $_, $newname\n";
  rename $_, $newname;
}

输出

rename foo-bar-(ab-4529111094).txt, foo-bar.txt
rename foo-bar-foo-bar-(ab-189534).txt, foo-bar-foo-bar.txt
rename foo-bar-foo-bar-bar-(ab-24937932201).txt, foo-bar-foo-bar-bar.txt

当前目录只有一级,没有子目录。我会尝试的,谢谢。 - SilverShadow
这段代码将会进入子目录,而这不是你想要的。 - Borodin
1
我已经添加了一个解决方案,只能重命名当前目录中的文件。 - Borodin
如果目录中有两个文件名分别为 foo-bar-(ab-4529111094).txtfoo-bar-(ab-189534).txt,会发生什么? - Kenosis

1
一个更简单、更短(更好?:))的rename正则表达式:

rename 's@-\(.*?\)@@' foo*.txt

不起作用,抱歉,请注意文件名开头的字符串"foo"可以是由破折号分隔的多个单词。 - SilverShadow
@sputnick所写的代码可以适用于任何以文件名开头的内容,只要它后面有一个连字符、一个左括号和(稍后)一个右括号。你是否尝试创建你在问题中提到的确切文件名,并查看它们是否可行? - Smylers

1

请检查这个:

ls -1 | nawk '/foo-bar-/{old=$0;gsub(/-\(.*\)/,"",$0);system("mv \""old"\" "$0)}'

> ls -1 foo*
foo-bar-(ab-4529111094).txt
foo-bar-foo-bar-(ab-189534).txt
foo-bar-foo-bar-bar-(ab-24937932201).txt

> ls -1 | nawk '/foo-bar-/{old=$0;gsub(/-\(.*\)/,"",$0);system("mv \""old"\" "$0)}'

> ls -1 foo*
foo-bar-foo-bar-bar.txt
foo-bar-foo-bar.txt
foo-bar.txt
> 

如需详细说明,请查看此处


0

另一种只使用Perl的方法:

perl -E'for (<*.*>){ ($new = $_) =~ s/(^.+?)(-\(.+)(\..*$)/$1$3/; say  $_." -> ".$new}'

(say ... 用于测试很好,只需将其替换为 rename $_,$newrename($_,$new))

  1. <*.*> 读取当前目录中的每个文件
  2. ($new = $_) =~ 将以下替换保存在 $new 中,并保留 $_ 不变
  3. (^.+?) 将此匹配保存在 $1 中,并从开头开始进行非贪婪匹配,直到...
  4. (-\(.+) 找到序列“-(...任何内容...)”。(此匹配将保存在 $2 中)
  5. (\..*$) 将最后一个“.”(句点)之前的所有内容(包括句点)保存到行末($)并包括行末 -> 到 $3 中
  6. 使用从 $1$3 生成的字符串替换匹配项

(您还可以使用 perl -E'for (</tmp/my/directory/*.*>){ ..... 对特定目录执行此操作)


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