如何使用查找重命名多个文件


37

我想使用命令find命令重命名多个文件(file1 ... filen到file1_renamed ... filen_renamed):

find . -type f -name 'file*' -exec mv filename='{}' $(basename $filename)_renamed ';'

但是得到这个错误:

mv: cannot stat filename=./file1’: No such file or directory

这不起作用,因为文件名未解释为外壳变量。

Answers:


46

以下是您的方法的直接解决方案:

find . -type f -name 'file*' -exec sh -c 'x="{}"; mv "$x" "${x}_renamed"' \;

但是,如果您有很多匹配文件,这将非常昂贵,因为您会mv为每个匹配项启动一个新的shell(执行)。而且,如果您在任何文件名中都有有趣的字符,它将爆炸。一种更有效,更安全的方法是:

find . -type f -name 'file*' -print0 | xargs --null -I{} mv {} {}_renamed

它还具有使用命名不正确的文件的好处。如果find支持的话,可以减少到

find . -type f -name 'file*' -exec mv {} {}_renamed \;

xargs不使用时{},该版本很有用

find .... -print0 | xargs --null rm

在这里rm被调用一次(或多次调用很多文件),但不是每个文件都调用一次。

我删除了basename你的问题,因为它可能是错误的:你会移动foo/bar/file8file8_renamed,不是foo/bar/file8_renamed

编辑(如评论中所建议):

  • 加短findxargs
  • 添加了安全贴纸

x没有用的情况下:find . -type f -name 'file*' -exec mv {} "{}_renamed" \; xargs版本具有与第一个示例相同的效率/
Costas 2015年

x仅在那里直接解决提问者的做法。
Thomas Erker

2
(1){}直接在shell(sh -c "…")命令中使用非常危险-您应该始终将其作为参数传递。(2)并非所有版本的find支持该{}_renamed构造。(3)我不理解您的声明xargs对删除文件(与重命名文件相反)有用。
G-Man说'Resstate Monica''s

@ G-Man:与的区别xargs不是mvvs. rm,而是使用{}vs.with。前者与mv file1 file1_renamed; mv file2 file2_renamed后者相似rm file1 file2
Thomas Erker

1
然后如果您想将_renamed文件改回其原始名称,您将如何做?
mcmillab

17

在尝试了第一个答案并进行了一点玩弄之后,我发现可以使用以下方法来做得更短,更简单-execdir

find . -type f -name 'file*' -execdir mv {} {}_renamed ';'

看起来它也应该完全满足您的需求。


2
通过find支持-execdir{}不是整体的实现,它也是最安全的。您可能希望将添加-imv,虽然(和-T如果mv支持的话)
斯特凡Chazelas

1
@StéphaneChazelas甚至更好,而不是依赖于mv提示或除了它之外,您还可以(无疑取决于您find对它的实现是否支持)使用-okdir它来输出要在执行之前执行的命令。
Matijs

值得一提的-depth是,如果您还要触摸目录名称,那也是个好主意。
Ciro Santilli新疆改造中心法轮功六四事件

哎呀,刚发现-execdir确实有一个很讨厌的缺点,find拒绝做任何事情,如果PATH包含任何相对路径... askubuntu.com/questions/621132/... find: The relative path XXX is included in the PATH environment
西罗桑蒂利新疆改造中心法轮功六四事件

6

另一种方法是while readfind输出上使用循环。这样就可以将每个文件名作为变量进行访问,而不必担心sh -c使用find-exec选项生成单独的进程会产生额外的成本/潜在的安全性问题,因此可以对其进行操作。

find . -type f -name 'file*' |
    while IFS= read file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

并且,如果所使用的外壳支持-d指定read分隔符的选项,则可以使用以下命令支持名称奇怪的文件(例如,带有换行符):

find . -type f -name 'file*' -print0 |
    while IFS= read -d '' file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

3

我想扩展第一个答案,请注意,由于./路径前缀存在于filename参数中,因此无法将其附加到文件名。

修改Thomas Erker的答案,我发现这是一种更通用的方法

find . -name PATTERN -printf "%f\0" | xargs --null -I{} mv {} "prefix {} suffix"

xargs选项:

--null指示传递的每个参数stdin以空字符(\0)结尾。这样,文件名可以包含空格,否则每个单词将被威胁为mv命令的不同参数。

-I replace-str的每次出现replace-str都会替换为从中读取的参数stdin。因此,如果需要,可以将其更改为其他字符串。


为什么用pringf "%f\0"而不是print0
slm

1
@sim,因为如果包含外壳元字符-print0会产生./前缀,这些PATTERN外壳元字符在重命名为带有原始名称的前缀时会受到阻碍。(例如重命名0 - foo.txt00 - foo.txt1 - bar.txt01 - bar.txt等)
DAS-克

2

我能够做的类似的东西forfindmv

for i in $(find . -name 'config.yml'); do mv $i $i.bak; done

这将找到所有config.yml文件并将其重命名为config.yml.bak


这具有能够使用可变扩展的优点。$ {i:r}。好答案。
萨拉米

是的,您几乎可以在中放置任何表达式for并执行批量操作。
Varun Risbud,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.