尝试将多个文件名的部分转换为小写时,重命名返回“不允许使用裸字”。

我在我的Ubuntu 16.04上的一个文件夹里有两个文件。
a1.dat
b1.DAT
我想将b1.DAT重命名为b1.dat,这样在文件夹中就会得到以下文件:
a1.dat
b1.dat

我尝试过(但没有成功):

$ rename *.DAT *.dat
Bareword "b1" not allowed while "strict subs" in use at (user-supplied code).
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).

并且

$ find . -iname "*.DAT" -exec rename DAT dat '{}' \;
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).
在搜索这个问题时,没有找到有意义的解决方案...

你可能也想了解mmv。 - PlasmaHH
5个回答

那个错误看起来像是来自Perl的rename。你需要使用引号,但只需要指定你想要更改的部分,类似于搜索和替换的方式。语法如下:
rename -n 's/\.DAT/\.dat/' *
在测试后删除-n以实际重命名文件。 在运行命令之前,更改glob设置以包括隐藏文件。
shopt -s dotglob
如果你想要递归地重命名文件,你可以使用以下方法。
shopt -s globstar
rename -n 's/\.DAT/\.dat/' **
或者,如果当前目录中或其子目录中有许多路径不以.DAT结尾,最好在第二个命令中指定这些路径。
rename -n 's/\.DAT/\.dat/' **/*.DAT

如果您的文件具有不以.DAT结尾的各种不同名称,这将更快。[1]

要关闭这些设置,您可以使用shopt -u,例如shopt -u globstar,但它们默认是关闭的,并且在打开新的shell时也会关闭。

如果导致参数列表过长,您可以使用例如find

find -type f -name "*.DAT" -exec rename -n -- 's/\.DAT/\.dat/' {} \;
或者更好
find -type f -name "*.DAT" -exec rename -n -- 's/\.DAT/\.dat/' {} +
使用find ... -exec+一起使用比使用\;更快,因为它从找到的文件构建了一个参数列表。最初我以为你不能使用它,因为你提到你遇到了一个argument list too long的问题,但现在我知道这个列表也会被巧妙地分成多个命令调用,以避免这个问题[2]。 由于rename将以相同的方式处理每个文件名,所以无论参数列表有多长都没有关系,因为它可以安全地分割成多个调用。如果你使用-exec的命令不接受多个参数,或者需要参数按特定顺序传递,或者由于其他原因分割参数列表会导致意外发生,那么你可以使用\;,这会导致命令对每个找到的文件都被调用一次(如果参数列表对其他方法来说太长,这将花费很长时间!)。
非常感谢Eliah Kagan提供的非常有用的建议来改进这个答案: [1] 在通配符匹配时指定文件名。 [2] find ... -exec使用+分割参数列表

谢谢。如何递归地执行此操作(对于所有子文件夹中的所有文件)? - Samo
@Samo 看看我的修改 :) - Zanna
无法工作:$ rename 's/.DAT/.dat/' ** bash: /usr/bin/rename: 参数列表太长 - Samo
@Samo 喔,好的。有很多文件。你可以使用 find (再次编辑!) - Zanna
很遗憾,这个方法不起作用。命令运行时没有出现错误,但文件名并未改变。 - Samo
1@Samo 在测试后需要从重命名中删除“-n” - 这只是为了显示将要更改的内容。 - Zanna
1在Centos和Arch上,重命名不会出现这种行为。我是通过在WSL上使用Debian来到这里的。这个语法是有效的。 - xtian

你可以做:

rename -n 's/DAT$/\L$&/' *.DAT
删除 -n 以进行实际重命名。
  • 全局模式 *.DAT 匹配当前目录中以 .DAT 结尾的所有文件

  • rename 的替换中,DAT$ 匹配末尾的 DAT

  • \L$& 将整个匹配转为小写;$& 引用整个匹配

如果只想对 b1.DAT 进行操作:

rename -n 's/DAT$/\L$&/' b1.DAT

例子:

% rename -n 's/DAT$/\L$&/' *.DAT
rename(b1.DAT, b1.dat)

其他 答案 已经解决了这个问题的两个主要方面之一:即如何成功地进行所需的重命名操作。本答案的目的是解释为什么您的命令不起作用,并包括了在 rename 命令上下文中那个奇怪的 "bareword not allowed" 错误消息的含义。

本答案的第一部分涉及 rename 和 Perl 之间的关系,以及 rename 如何使用您传递给它的第一个命令行参数,即它的代码参数。第二部分涉及 shell 如何执行扩展-特别是 globbing-来构建参数列表。第三部分介绍了在 Perl 代码中发生 "Bareword not allowed" 错误的情况。最后,第四部分总结了从输入命令到出现错误所经历的所有步骤。

1. 当 rename 给出奇怪的错误消息时,将 "Perl" 添加到您的搜索中。

在Debian和Ubuntu中,rename命令是一个Perl脚本,用于执行文件重命名操作。在旧版本中(包括14.04 LTS,截至本文撰写时仍得到支持),它是一个指向prename命令的符号链接(间接指向)。在新版本中,它指向更新的file-rename命令。这两个Perl重命名命令的工作方式基本相同,我将在本答案的其余部分统称为rename。 当你使用rename命令时,你不仅仅是运行别人编写的Perl代码。你还要编写自己的Perl代码,并告诉rename去运行它。这是因为传递给rename命令的第一个命令行参数,除了选项参数-n之外,实际上包含了真正的Perl代码rename命令使用这段代码来操作你作为后续命令行参数传递给它的每个路径名。(如果你没有传递任何路径名参数,那么rename会从标准输入中读取路径名,每行一个。) 代码在循环内运行,每个路径名执行一次迭代。在每个循环迭代的顶部,在您的代码运行之前,特殊的{{link3:$_变量}}将被分配到当前正在处理的路径名。如果您的代码导致$_的值更改为其他内容,则该文件将重命名为新名称。 在Perl中,许多表达式在没有其他表达式可用作操作数时,隐式地作用于$_变量。例如,替换表达式$str =~ s/foo/bar/会将$str 变量中的字符串中第一次出现的foo改为bar,如果不包含foo则保持不变。如果只写s/foo/bar/而没有明确使用=~运算符,那么它将作用于$_。这意味着s/foo/bar/等同于$_ =~ s/foo/bar/

通常将一个s///表达式作为代码参数(即第一个命令行参数)传递给rename是很常见的,但你并不一定要这样做。你可以给它任何你希望在循环内运行的Perl代码,以检查每个$_的值,并(有条件地)修改它。

这有很多酷和有用的后果, 但其中绝大部分超出了本问题和答案的范围。我在这里提到这一点的主要原因--事实上,我决定发布这个答案的主要原因--是为了强调,因为rename的第一个参数实际上是Perl代码,所以每当你遇到奇怪的错误消息并且在搜索中找不到相关信息时,你可以在搜索字符串中添加"Perl"(有时甚至替换"rename"为"Perl"),通常会找到答案。

2. 使用rename *.DAT *.dat命令时,rename命令从未看到*.DAT

rename s/foo/bar/ *.txt这样的命令通常不会将*.txt作为命令行参数传递给rename程序,除非你有一个文件名确实为*.txt,但希望你没有这样的文件。

rename不会像*.txt*.DAT*.datx**y*这样,当作为路径名参数传递给它时,解释通配符模式相反,你的shell会对它们进行路径名展开(也称为文件名展开,也称为globbing)。这发生在运行rename实用程序之前。Shell将通配符展开为潜在的多个路径名,并将它们作为单独的命令行参数传递给rename。在Ubuntu中,你的交互式shell是Bash,除非你已经更改了它,这就是为什么我在上面链接到了Bash参考手册

有一种情况下,可以将全局模式作为单个未展开的命令行参数传递给rename:当它不匹配任何文件时。不同的shell在这种情况下表现出不同的默认行为,但是Bash的默认行为是直接将全局模式传递过去。然而,你很少会想要这样做!如果你确实想要这样做,那么你应该通过引用来确保模式不被展开。这适用于传递参数给任何命令,不仅仅是rename。 引用不仅仅是为了文件名扩展(globbing),因为你的shell还会对未引用的文本进行其他扩展,而且对于其中一些但不是全部,也会对用" "引起来的文本进行处理。总的来说,每当你想传递一个参数,其中包含可能被shell特殊处理的字符,包括空格时,最好用' '引号引起来。 Perl代码s/foo/bar/不包含任何被shell特殊处理的内容,但是对我来说最好也将其引用起来,并写成's/foo/bar/'。(实际上,我之所以没有这样做,是因为对一些读者来说会产生困惑,因为我还没有讲到引用的问题。)我之所以说这样做是好的,是因为Perl代码通常会包含这样的字符,如果我要修改这段代码,可能就会忘记检查是否需要引用。相反,如果你希望shell扩展一个通配符,就不能将其引用起来。 3. Perl解释器中"bareword not allowed"的意思 你在问题中显示的错误信息表明,当你运行rename *.DAT *.dat时,你的shell将*.DAT扩展为一个或多个文件名的列表,而第一个文件名是b1.DAT。所有后续的参数--无论是从*.DAT扩展出来的还是从*.dat扩展出来的--都在该参数之后,因此它们将被解释为路径名。 因为实际运行的是类似于rename b1.DAT ...这样的命令,而且由于rename将其第一个非选项参数视为Perl代码,所以问题就变成了:为什么当你将b1.DAT作为Perl代码运行时会产生这些"bareword not allowed"错误?
Bareword "b1" not allowed while "strict subs" in use at (user-supplied code).
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).
简而言之,我们在引用字符串时使用保护它们,以防止意外的shell扩展将其自动转换为其他字符串(请参见上面的部分)。Shell是特殊用途的编程语言,与通用目的语言非常不同(它们的非常奇怪的语法和语义反映了这一点)。但Perl是一种通用目的的编程语言,像大多数通用目的编程语言一样,在Perl中引用的主要目的不是为了保护字符串,而是提及它们。实际上,这是大多数编程语言与自然语言相似的一种方式。在英语中,假设你有一只狗,“your dog”是一个由两个单词组成的短语,而your dog则是一只狗。类似地,在Perl中,'$foo'是一个字符串,而$foo是一个名为$foo的东西。 然而,与几乎所有其他通用编程语言不同,Perl有时会将未加引号的文本解释为提到一个字符串--一个与它相同的字符串,意思是由相同的字符按相同的顺序组成。只有在它找不到其他含义时,它才会尝试以这种方式解释代码,前提是它是一个裸字(没有$或其他符号,请参见下文)。然后它会将其作为一个字符串处理,除非您通过启用restrictions来告诉它不要这样做。 在Perl中,变量通常以一个称为“sigil”的标点字符开头,该标点字符指定变量的广义类型。例如,$表示scalar@表示array%表示hash。(还有其他类型。)如果你觉得这很混乱(或无聊),不要担心,因为我只是提起来说明一个问题:当Perl程序中出现一个有效名称但没有前导标点字符时,该名称被称为bareword。 裸字有各种用途,但通常表示程序中定义的内建函数用户自定义子例程(或由程序使用的模块中定义的)。Perl没有名为b1DAT的内建函数,因此当Perl解释器看到代码b1.DAT时,它尝试将b1DAT视为子例程的名称。假设未定义这样的子例程,这将失败。然后,如果限制未启用,它将把它们视为字符串。这会起作用,但你是否真的打算这样做就不得而知了。Perl的.操作符连接字符串,因此b1.DAT求值为字符串b1DAT。也就是说,b1.DAT是一种不好的写法,类似于'b1' . 'DAT'"b1" . "DAT"。 你可以通过运行命令perl -E 'say b1.DAT'来自己测试一下,这个命令会将短的Perl脚本say b1.DAT传递给Perl解释器来运行,并打印出b1DAT。(在这个命令中,' '引号告诉shell将say b1.DAT作为一个单独的命令行参数传递;否则,空格会导致sayb1.DAT解析为不同的单词,并且perl会将它们作为不同的参数接收。perl并不会看到引号本身,因为shell会去掉它们。) 但是现在,在Perl脚本中在say之前尝试写入use strict;。 现在它与您从rename获得的相同类型的错误失败:
$ perl -E 'use strict; say b1.DAT'
Bareword "b1" not allowed while "strict subs" in use at -e line 1.
Bareword "DAT" not allowed while "strict subs" in use at -e line 1.
Execution of -e aborted due to compilation errors.
这是因为use strict;禁止了Perl解释器将裸字视为字符串。要禁止这个特定的功能,只需要启用subs限制就足够了。这个命令会产生与上面相同的错误:
perl -E 'use strict "subs"; say b1.DAT'
通常Perl程序员只会写use strict;,这样就启用了subs限制和其他两个限制。一般来说,推荐使用use strict;。所以rename命令是为了你的代码而存在的。这就是为什么你会收到那个错误信息的原因。 4. 总结一下,发生了以下情况:
  1. 您的shell将b1.DAT作为第一个命令行参数传递,rename将其视为Perl代码在循环中运行每个路径名参数。
  2. 这意味着b1DAT.操作符相连。
  3. b1DAT没有前缀符号,因此它们被视为裸字。
  4. 这两个裸字将被视为内置函数或任何用户定义的子例程的名称,但是没有任何东西有这些名称。
  5. 如果没有启用"strict subs",则它们将像字符串表达式'b1''DAT'一样处理并连接起来。这远非您想要的结果,这说明了这个特性通常是无助的。
  6. 但是"strict subs"已启用,因为rename启用了所有限制varsrefssubs)。因此,您会收到一个错误。
  7. rename由于此错误而退出。因为这种类型的错误发生得很早,即使您没有传递-n,也不会进行文件重命名尝试。这是一件好事,经常保护用户免受意外的文件名更改,有时甚至免受实际数据丢失。
感谢Zanna,她帮助我解决了这个答案早期草稿中的几个重要问题。没有她,这个答案可能会变得更加难以理解,甚至可能不会被发布。

深入剖析了核心要点的解释,以一种我希望所有帮助和信息文本都能采用的风格进行了书写。非常感谢您提供这个答案。它对我更好地理解贝壳和珍珠有很大帮助。 - Claudio

你可以这样做:
 rename 'y/A-Z/a-z/' *

我刚遇到了同样的错误。所以我来到这里,看到了一些在Linux环境中对新手来说可能很典型的东西。 我只想要一个简短的命令,更容易记住。
mv Greetings Greetings.java
这就是了。mv命令将文件从Greetings重命名为Greetings.java。 没有更多的Greetings文件了。只有一个准备好使用javac编译的Greetings.java文件。