我正在编写一个脚本,该脚本将查找并用下划线替换除[^A-Za-z0-9._-]
特定文件类型(在本例中)之外的任何字符。我希望它从当前路径递归执行。我正在使用Ubuntu。.avi
_
我不想或不需要更改任何文件夹名称,因为我在创建时可以控制这些名称。这是我参考以下链接后现在得到的:
find . -depth -exec rename 's!([^/]*\Z)/[^A-Za-z0-9._-]/_/g' *.avi {} +
我显然错过了一些东西。请帮忙。 :)
答案1
为什么你的代码不起作用
通配符模式由运行之前*.avi
运行的 shell 展开,因此其效果取决于当前目录中是否有文件。看find
find
*.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
是为了试运行。高兴的时候就删掉。
工作解决方案find
与rename
使用基于 perl 的变体rename
和find
支持的实现-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
加快速度)。 - 使用
-execdir
,find
运行命令在目录中包含这些文件并将相对于该目录的路径传递给命令。有些find
实现(GNU 实现)会./
向文件添加前缀,有些则不会。 do的一些变体rename
接受选项后perl 表达式,这意味着如果您有一个名称以 开头的文件-
,则可能会导致问题。 - 即使文件名包含字节序列,否则我们也必须使用
LC_ALL=C
for-name
才能在语言环境中形成有效字符。rename
继承了这一点,并且无论如何在大多数变体中仅适用于 ASCII。然而,这意味着它将用与_
字符具有的字节数相同的多字节字符来替换多字节字符。例如,它将 UTF-8 重命名stéphane
为.是可以的,因为它会将多字节字符和所有无法解码为字符的字节分别转换为一个字符。st__phane
st_phane
zsh
_
- 与
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
。