其他 答案 已经解决了这个问题的两个主要方面之一:即如何成功地进行所需的重命名操作。本答案的目的是解释为什么您的命令不起作用,并包括了在 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
、*.dat
、x*
、*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没有名为b1
或DAT
的内建函数,因此当Perl解释器看到代码b1.DAT
时,它尝试将b1
和DAT
视为子例程的名称。假设未定义这样的子例程,这将失败。然后,如果限制未启用,它将把它们视为字符串。这会起作用,但你是否真的打算这样做就不得而知了。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
作为一个单独的命令行参数传递;否则,空格会导致say
和b1.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. 总结一下,发生了以下情况:
- 您的shell将
b1.DAT
作为第一个命令行参数传递,rename
将其视为Perl代码在循环中运行每个路径名参数。
- 这意味着
b1
和DAT
与.
操作符相连。
b1
和DAT
没有前缀符号,因此它们被视为裸字。
- 这两个裸字将被视为内置函数或任何用户定义的子例程的名称,但是没有任何东西有这些名称。
- 如果没有启用"strict subs",则它们将像字符串表达式
'b1'
和'DAT'
一样处理并连接起来。这远非您想要的结果,这说明了这个特性通常是无助的。
- 但是"strict subs"已启用,因为
rename
启用了所有限制(vars
、refs
和subs
)。因此,您会收到一个错误。
rename
由于此错误而退出。因为这种类型的错误发生得很早,即使您没有传递-n
,也不会进行文件重命名尝试。这是一件好事,经常保护用户免受意外的文件名更改,有时甚至免受实际数据丢失。
感谢Zanna,她帮助我解决了这个答案早期草稿中的几个重要问题。没有她,这个答案可能会变得更加难以理解,甚至可能不会被发布。