使用重命名递归替换文件名(而不是目录)中的无效字符的脚本

使用重命名递归替换文件名(而不是目录)中的无效字符的脚本

我正在编写一个脚本,该脚本将查找并用下划线替换除[^A-Za-z0-9._-]特定文件类型(在本例中)之外的任何字符。我希望它从当前路径递归执行。我正在使用Ubuntu。.avi_

我不想或不需要更改任何文件夹名称,因为我在创建时可以控制这些名称。这是我参考以下链接后现在得到的:

SE参考

find . -depth -exec rename 's!([^/]*\Z)/[^A-Za-z0-9._-]/_/g' *.avi {} +

我显然错过了一些东西。请帮忙。 :)

答案1

为什么你的代码不起作用

通配符模式由运行之前*.avi运行的 shell 展开,因此其效果取决于当前目录中是否有文件。看findfind*.avi当文件位于顶部时查找不递归以获得更多解释。要在子目录中展开*.avi,您需要做三件不同的事情:引用模式,以便原始 shell 不会展开它;安排在每个子目录中运行一个额外的 shell 来执行通配符扩展;并仅使用find命令而不是任何文件类型查找目录。

此外,您的代码最终会rename通过{} +.因此rename对目录进行操作,而不仅仅是常规文件。

此外,您的 Perl 代码中存在语法错误。

使用 zsh 的工作解决方案

autoload -Uz zmv # best in ~/.zshrc

zmv -n '(**/)(*.avi)(#qD^/)' '$1${2//[^a-zA-Z0-9._-]/_}'

^/是选择除目录。替换.常规的仅文件。-n是为了试运行。高兴的时候就删掉。

工作解决方案findrename

使用基于 perl 的变体renamefind支持的实现-execdir

LC_ALL=C find . -depth -name '*[!a-zA-Z0-9._-]*.avi' ! -type d -execdir \
  rename 's/[^a-zA-Z0-9._-]/_/g' {} +

但这种方法有一些注意事项:

  • 运行至少rename每个包含要重命名的文件的目录有一个实例(rename每个文件一个,具有某些find实现/版本,-execdir ... {} +实际上与 . 相同-execdir ... {} \;。(每个文件zmv运行一个mv,但您可以使用mv内置命令来zmodload zsh/files加快速度)。
  • 使用-execdirfind运行命令在目录中包含这些文件并将相对于该目录的路径传递给命令。有些find实现(GNU 实现)会./向文件添加前缀,有些则不会。 do的一些变体rename接受选项perl 表达式,这意味着如果您有一个名称以 开头的文件-,则可能会导致问题。
  • 即使文件名包含字节序列,否则我们也必须使用LC_ALL=Cfor-name才能在语言环境中形成有效字符。rename继承了这一点,并且无论如何在大多数变体中仅适用于 ASCII。然而,这意味着它将用与_字符具有的字节数相同的多字节字符来替换多字节字符。例如,它将 UTF-8 重命名stéphane为.是可以的,因为它会将多字节字符和所有无法解码为字符的字节分别转换为一个字符。st__phanest_phanezsh_
  • zsh's相反,在开始重命名之前zmv,它不会执行健全性检查(例如,两个文件最终不会具有相同的名称,如a+b.avi和)。但是不应覆盖现有文件。[email protected]rename

答案2

假如说

rename 's/[^A-Za-z0-9.-]/_/g' -- *.avi

将正确重命名当前目录中带有文件名后缀的所有文件.avi,您可以将其应用于当前目录下的所有目录(包括当前目录)

find . -type d -exec sh -c '
    for dirpath do
        ( cd "$dirpath" && rename "s/[^A-Za-z0-9.-]/_/g" -- *.avi )
    done' sh {} +

对于批量找到的目录,这将执行一个简短的内联脚本,该脚本更改为找到的目录并执行命令rename

相关内容