str*_*gee 2 shell find xargs rename
我有一个 etckeeper 的 git clone,我正在尝试将名称中的所有文件和目录重命名etckeeper为usrkeeper. 例如,./foo-etckeeper-bar应该重命名为./foo-usrkeeper-bar.
查找相关文件很简单:
% find . -path '*etckeeper*' -print
Run Code Online (Sandbox Code Playgroud)
但是,我无法弄清楚如何实际进行重命名。我想结合xargs使用mv:
% find . -path '*etckeeper*' -print0 | xargs -0 -n 1 -J % bash -c mv % '$(echo' % \| sed \"s/etckeeper/usrkeeper/\" \)
Run Code Online (Sandbox Code Playgroud)
为了可读性,非转义的后半部分为:xargs -0 -n 1 -J % bash -c mv % $(echo % | sed "s/etckeeper/usrkeeper/" ). 其背后的想法是我们使用$()管道将文件名通过sed,用于进行替换。
这里的问题是bash -c要求执行的命令是单个字符串。之后,它开始将参数解释为位置参数。我可以引用整件事:
% find . -path '*etckeeper*' -print0 | xargs -0 -n 1 -J % bash -c 'mv % $(echo % | sed "s/etckeeper/usrkeeper/g" )'
Run Code Online (Sandbox Code Playgroud)
但现在xargs不会取代%. 我该如何解决这个问题?(另外,作为旁注,如果在包含etckeeper名称的目录中有包含名称的文件,则上述操作将失败etckeeper,因为该目录将在文件之前移动。)
小智 5
您可以使用重命名命令(请参阅编辑 1)。
解决方案1
对于合理数量的文件/目录,通过设置 bash 4 选项globstar(不适用于递归名称,请参阅编辑 3):
shopt -s globstar
rename -n 's/etckeeper/userkeeper/g' **
Run Code Online (Sandbox Code Playgroud)
解决方案2
对于使用 rename 和 find 分两步查找的大量文件/目录,以防止重命名刚刚重命名的目录中的文件失败(请参阅编辑 2):
find . -type f -exec rename 's/etckeeper/userkeeper/g' {} \;
find . -type d -exec rename 's/etckeeper/userkeeper/g' {} \;
Run Code Online (Sandbox Code Playgroud)
编辑 1:
有两种不同的重命名命令。此答案使用基于 Perl 的重命名命令,可在基于 Debian 的发行版中使用。要在非基于 Debian 的发行版上安装它,您可以从 cpan 安装或获取它。
编辑2:
正如 Jeff Schaller 在评论中指出的-depthfind 选项,Process each directory's contents before the directory itself因此只有“有序” find by-depth选项就足够了:
find . -depth -exec rename 's/etckeeper/userkeeper/g' {} \;
Run Code Online (Sandbox Code Playgroud)
编辑 3
解决方案 1 不适用于递归重命名目标,(例如etckeeper/etckeeper/etckeeper)因为在内部级别之前处理外部级别并且指向内部级别的指针变得无用。(在第一次重命名etckeeper/etckeeper/etckeeper后将被命名,usrkeeper/etckeeper/etckeeper因此重命名为etckeeper/etckeeper/和etckeeper/etckeeper/etckeeper将失败)。固定在同一问题find的-depth选项。
编辑4
正如 cas 在评论中指出的那样,我会使用 {} + 而不是 {} \; - 多次重命名 perl 脚本(每个文件/目录一次)是昂贵的。