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

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

我的 Ubuntu 16.04 上的文件夹中有两个文件:

a1.dat
b1.DAT

我想重命名b1.DATb1.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).

搜索此问题没有得到任何有意义的解决方案......

答案1

该错误看起来像是来自 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 ... -execwith+比 using 更快,\;因为它会根据找到的文件构建参数列表。最初我以为你不可能使用它,因为你提到你遇到了问题argument list too long,但现在我知道列表也会根据需要巧妙地分解为命令的多个调用,以避免出现该问题[2]

由于rename将以相同的方式处理每个文件名,因此参数列表的长度无关紧要,因为它可以安全地拆分到多个调用中。如果您使用的命令-exec不接受多个参数或要求其参数按特定顺序排列,或者由于任何其他原因拆分参数列表会导致发生不必要的事情,则可以使用\;,这会导致对找到的每个文件调用一次命令(如果参数列表对于其他方法来说太长,这将需要很长时间!)。


非常感谢伊莱亚·卡根对于改进这个答案的非常有用的建议:

[1] 通配符时指定文件名
[2] find ... -execwith+拆分参数列表

答案2

你可以做:

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

删除-n以便进行实际的重命名。

  • glob 模式匹配当前目录中*.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)

答案3

其他 答案已经解决了这个问题的两个主要方面之一:如何成功执行您所需的重命名操作。此答案的目的是解释为什么您的命令不起作用,包括命令上下文中奇怪的“不允许使用 bareword”错误消息的含义rename

rename本回答的第一部分是关于Perl 和 Python之间的关系,以及它如何rename使用您传递给它的第一个命令行参数,即其代码参数。第二部分是关于 shell 如何执行扩展(特别是通配符)来构造参数列表。第三部分是关于 Perl 代码中出现“不允许使用 Bareword”错误的原因。最后,第四部分总结了从输入命令到出现错误的所有步骤。

1. 当rename出现奇怪的错误消息时,请在搜索中添加“Perl”。

在 Debian 和 Ubuntu 中,该rename命令是Perl执行文件重命名的脚本。在较旧的版本中(包括 14.04 LTS,它仍然支持的截至撰写本文时——这是符号链接指向(间接)到prename命令。在较新的版本中,它指向较新的file-rename命令。那些两个 Perl 重命名命令工作方式大致相同,rename在本答案的其余部分中,我将同时引用它们。

当你使用该rename命令时,你不仅仅是在运行别人编写的 Perl 代码。你还在编写自己的 Perl 代码并告诉它rename运行它。这是因为首先命令行参数你传递给rename命令,除了选项参数喜欢-n由实际的 Perl 代码组成。该rename命令使用此代码对每个路径名将其作为后续命令行参数传递。(如果您不传递任何路径名参数,rename则从标准输入而是每行一个。)

代码在里面运行一个循环, 哪个迭代每个路径名一次。在循环的每次迭代顶部,在代码运行之前,特殊的$_多变的被分配当前正在处理的路径名。如果您的代码导致的值$_更改为其他值,则该文件将被重命名为新名称。

许多表达式在 Perl 中,$_当没有给出任何其他表达式作为操作数。例如,替换表达式将字符串中$str =~ s/foo/bar/第一次出现的foo$str 多变的bar,或如果不包含则保持不变foo。如果你只是写s/foo/bar/而不明确使用运营=~,则它对 进行运算$_。也就是说 是s/foo/bar/的简写$_ =~ s/foo/bar/

这是很常见的一种s///表达作为rename代码参数(即第一个命令行参数),但您不必这样做。您可以为其提供任何您希望它在循环内运行的 Perl 代码,以检查每个值$_并(有条件地)修改它。

这有很多很酷且有用的后果,但其中绝大多数都超出了这个问题和答案的范围。我在这里提出这个问题的主要原因——事实上,我决定发布这个答案的主要原因——是为了说明这一点,因为的第一个参数rename实际上是 Perl 代码,所以每当你收到一个奇怪的错误消息并且你无法通过搜索找到有关它的信息时,你可以将“Perl”添加到搜索字符串中(有时甚至可以将“rename”替换为“Perl”),你通常就会找到答案。

2. 使用rename *.DAT *.datrename命令永远不会看到*.DAT

类似这样的命令rename s/foo/bar/ *.txt通常不会*.txt作为命令行参数传递给rename程序,你不想,除非您有一个文件,其名称实际上是*.txt,但希望您没有。

rename不解释全局模式*.txt*.DAT*.datx**y*当作为路径名参数传递给它时。反而,你的 shell 执行路径名扩展在这些路径上执行命令(也称为文件名扩展,也称为通配符)。这发生在rename实用程序运行之前。shell 将通配符扩展为多个路径名,并将它们作为单独的命令行参数传递给rename。在 Ubuntu 中,您的交互式 shell 是猛击,除非你改变了它,这就是为什么我链接到Bash 参考手册多于。

有一种情况是,当 glob 模式与任何文件都不匹配时,可能会将其作为单个未展开的命令行参数传递给rename:。在这种情况下,不同的 shell 会表现出不同的默认行为,但 Bash 的默认行为是直接传递 glob。但是,您很少需要这样做!如果您确实需要它,那么您应该确保模式没有扩大,引用它。这适用于将参数传递给任何命令,而不仅仅是rename

引用不仅仅用于文件名扩展,因为还有其他扩展你的 shell 对未加引号的文本执行的操作,以及有些是,但其他不是,也适用于引号内的文本" "。一般来说,每当你想要传递一个包含可能被 shell 特殊处理的字符(包括空格)的参数时,你都应该用引号引起来,最好用' '引号

Perl 代码s/foo/bar/不包含任何由 shell 特殊处理的内容,但对我来说,引用这些代码也是个好主意——并写下's/foo/bar/'。(事实上,我没有这样做的唯一原因是,这会让一些读者感到困惑,因为我还没有谈到引用。)我之所以这么说,是因为 Perl 代码包含这样的字符,如果我要更改该代码,我可能忘记检查是否需要引用。相反,如果你想让 shell 扩展 glob,它必须不是被引用。

3. Perl 解释器中“不允许使用裸字”的含义

您在问题中显示的错误消息表明,当您运行 时rename *.DAT *.dat,您的 shell 扩展*.DAT为一个或多个文件名的列表,并且第一的这些文件名是b1.DAT。所有后续参数(包括任何其他扩展自的参数*.DAT和任何扩展自的*.dat参数)都位于该参数之后,因此它们将被解释为路径名。

因为实际运行的是类似于的东西rename b1.DAT ...,并且因为rename将其第一个非选项参数视为 Perl 代码,所以问题变成:为什么b1.DAT当您将它作为 Perl 代码运行时会产生这些“不允许使用裸字”错误?

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 扩展否则会自动将它们转换为其他字符串(参见上面的部分)。Shell 是专用编程语言,其工作方式与通用语言截然不同(以及他们的非常奇怪的语法和语义反映这一点)。但是 Perl 是一个通用编程语言和大多数通用编程语言一样,Perl 中引用的主要目的不是保护字符串,但根本不提它们。这实际上是大多数编程语言的一种方式相似的自然语言。在英语中,假设您有一只狗,“your dog” 是一个双词短语,而 your dog 是一只狗。同样,在 Perl 中,'$foo'是一个字符串,而$foo是名称为 的东西$foo

然而,与几乎所有其他通用编程语言不同,Perl 将有时将未引用的文本解释为提到一个字符串——一个与它“相同”的字符串,即由相同顺序的相同字符组成。它只会尝试以这种方式解释代码如果它是一个裸词(没有$或其他符号,见下文),并且找不到任何其他含义。然后它会将其视为字符串,除非你告诉它不要通过启用限制

Perl 中的变量通常以标点符号开头,称为符印,指定变量的广义类型。例如,$方法标量@方法大批,并且%意味着哈希。 (还有其他人。)如果你觉得这令人困惑(或无聊),不要担心,因为我只是想说当一个有效名称出现在 Perl 程序中,但不是前面有一个印记,这个名字被认为是裸词

裸词有多种用途,但它们通常表示内置函数用户定义子程序已在程序中(或程序使用的模块中)定义。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。(在该命令中,' '引号告诉 shellsay b1.DAT作为单个命令行参数传递;否则,空格会导致sayb1.DAT成为解析为单独的单词并将它们作为单独的参数perl接收。perl不是参见引文本身,壳把它们除去

但现在,尝试写use strict;在之前的 Perl 脚本中say。现在它会失败,并出现与以下相同的错误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. 您的 shellb1.DAT作为第一个命令行参数传递,该参数rename被视为 Perl 代码,并针对每个路径名参数循环运行。
  2. 这被认为意味着b1与操作员DAT相关.
  3. b1并且DAT没有以符号作为前缀,因此它们被视为裸词。
  4. 这两个裸词本来会被视为内置函数或任何用户定义的子程序的名称,但实际上并不存在具有上述任何名称的东西。
  5. 如果没有启用“strict subs”,那么它们将被视为字符串表达式'b1''DAT'和连接起来。这与您的意图相差甚远,这表明此功能通常没有帮助。
  6. 但“严格订阅”已启用,因为rename启用所有限制varsrefssubs)。因此,您得到的是错误。
  7. rename由于此错误而退出。由于此类错误发生得早,因此即使您没有通过,也不会进行文件重命名尝试-n。这是一件好事,它通常可以保护用户免受无意的文件名更改,有时甚至可以防止实际数据丢失。

感谢扎娜, WHO帮助我解决了这个答案早期草稿中的几个重要缺陷。如果没有她,这个答案就毫无意义,甚至可能根本不会被发布。

答案4

你可以这样做:

 rename 'y/A-Z/a-z/' *

相关内容