在Linux中查找多个文件并重命名它们

110

我在Suse 10系统中有像 a_dbg.txt,b_dbg.txt ... 这样的文件。 我想编写一个bash shell脚本,以通过从文件名中移除“_dbg”来重命名这些文件。

Google建议我使用rename命令。 因此,我在CURRENT_FOLDER上执行了命令rename _dbg.txt .txt *dbg*

我的实际CURRENT_FOLDER包含以下文件。

CURRENT_FOLDER/a_dbg.txt
CURRENT_FOLDER/b_dbg.txt
CURRENT_FOLDER/XX/c_dbg.txt
CURRENT_FOLDER/YY/d_dbg.txt

执行rename命令后,

CURRENT_FOLDER/a.txt
CURRENT_FOLDER/b.txt
CURRENT_FOLDER/XX/c_dbg.txt
CURRENT_FOLDER/YY/d_dbg.txt

这个命令不会递归地执行,如何使其可以重命名所有子目录中的文件。就像XXYY一样,我将拥有许多名称不可预测的子目录。而且我的CURRENT_FOLDER中还有其他文件。


8
我收到了 Bareword "..." not allowed while "strict subs" in use at (eval 1) line 1. 的错误信息。 - Albert Hendriks
6
为什么没有一个叫做“JUST rename -r”的工具,以简化我们的工作,而不是采用笨重的解决方案? - neverMind9
13个回答

164
你可以使用find递归查找所有匹配的文件:
find . -iname "*dbg*" -exec rename _dbg.txt .txt '{}' \;

编辑: 什么是'{}'\;?

-exec选项使得find命令针对每一个匹配到的文件执行rename命令。'{}'将会被替换为该文件的路径名。而最后面的\;仅仅是用来标识-exec表达式的结束。

所有这些在find命令的man page中都有详细的描述:

 -exec utility [argument ...] ;
         True if the program named utility returns a zero value as its
         exit status.  Optional arguments may be passed to the utility.
         The expression must be terminated by a semicolon (``;'').  If you
         invoke find from a shell you may need to quote the semicolon if
         the shell would otherwise treat it as a control operator.  If the
         string ``{}'' appears anywhere in the utility name or the argu-
         ments it is replaced by the pathname of the current file.
         Utility will be executed from the directory from which find was
         executed.  Utility and arguments are not subject to the further
         expansion of shell patterns and constructs.

2
我检查了一下,它只适用于文件而不是目录。 - Syed Mudabbir
18
对于那些收到 Bareword "..." not allowed while "strict subs" in use at ... 错误信息的用户, 请尝试运行命令: find . -iname "*BUILD*" -exec rename 's/_dbg.txt/.txt/' '{}' \;。该命令会将当前目录及其子目录下所有名称包含"BUILD"的文件中,后缀名为"_dbg.txt"的部分替换为".txt"。 - jsand
1
从手册中查找。-iname模式。类似于-name,但匹配不区分大小写。例如,模式“fo ”和“F?”匹配文件名“Foo”,“FOO”,“foo”,“fOo”等。模式“ foo *”也将匹配名为“.foobar”的文件。 - user43326
9
我收到“find:重命名:没有那个文件或目录”的提示信息。 - Bartek Pacia
4
@BartekPacia rename 是一个 Unix 实用程序(请参见 https://www.gnu.org/software/gnulib/manual/html_node/rename.html)。错误提示表明它没有安装在您的机器上。对于 macOS(通常情况下),您可以通过 brew install rename 来解决这个问题。 - vervas
显示剩余6条评论

29

我使用以下命令进行递归重命名:

find -iname \*.* | rename -v "s/ /-/g"

还可以用于恢复备份文件。find -iname \*.* | rename -v -f "s/\.bak//g" - Paolo
我的重命名没有-v选项。你是在Mac OS X上吗?我用的是Ubuntu 16。 - Katastic Voyage
-v 在我的“rename”工具版本中等同于 --verbose;对结果不重要。更多解释请键入“man rename”。 - Gantan
5
在Ubuntu 18.10上,这对我不起作用,因为“rename”尝试重命名整个路径,并且如果更改了父目录,则会失败。你必须像https://dev59.com/WWQn5IYBdhLWcg3w1Z9f#54163971中所示那样仅操作基本名称,并使用“-depth”按深度优先进行操作。 - Ciro Santilli OurBigBook.com
1
@CiroSantilliПутлерКапут六四事 就是那样。 - MrR
显示剩余2条评论

22

我写了一个小脚本,可以递归地将 /tmp 目录及其子目录中所有扩展名为 .txt 的文件替换为扩展名为 .cpp 的文件。

#!/bin/bash

for file in $(find /tmp -name '*.txt')
do
  mv $file $(echo "$file" | sed -r 's|.txt|.cpp|g')
done

8
破损的方法!请不要使用for file in $(find ...)。请参阅为什么不应解析ls(1)的输出 - gniourf_gniourf

16

使用Bash:

shopt -s globstar nullglob
rename _dbg.txt .txt **/*dbg*

5
很棒的解决方案。我需要使用 shopt -s globstar nullstar 来启用那些 Bash 选项。在 MacOS 上通过 brew install rename 安装的重命名工具,我需要执行 rename -s oldStuff newStuff **/* - Xander Dunn
当你没有 Perl 版本的重命名工具时,这是一个很好的解决方案。我不得不运行两次,因为需要重命名的一个项目是一个文件夹,这导致其中的其他重命名无法完成。 - undefined

13

find -execdir rename也适用于基于文件名的非后缀替换。

https://dev59.com/WWQn5IYBdhLWcg3w1Z9f#16541670 直接适用于后缀,但对于任意正则表达式替换在文件名上都可以使用此方法:

PATH=/usr/bin find . -depth -execdir rename 's/_dbg.txt$/_.txt' '{}' \;

或仅对文件产生影响:

PATH=/usr/bin find . -type f -execdir rename 's/_dbg.txt$/_.txt' '{}' \;

-execdir选项会首先进入目录,然后仅对基本名称执行操作。

在Ubuntu 20.04上测试过,适用于find 4.7.0和rename 1.10。

这是一个方便且更安全的帮助程序。

find-rename-regex() (
  set -eu
  find_and_replace="$1"
  PATH="$(echo "$PATH" | sed -E 's/(^|:)[^\/][^:]*//g')" \
    find . -depth -execdir rename "${2:--n}" "s/${find_and_replace}" '{}' \;
)

GitHub上游

示例用法,将空格“ ”替换为连字符“-”。

在不实际执行的情况下显示将被重命名为什么的干运行:

find-rename-regex ' /-/g'

做替换:

find-rename-regex ' /-/g' -v

命令说明

-execdir这一神奇的选项在执行rename命令之前会进入目录,与-exec不同。

-depth确保重命名首先发生在子级上,然后才是父级,以防止缺失父目录可能导致的问题。

需要使用-execdir,因为rename无法处理非基本名称的输入路径,例如以下操作会失败:

rename 's/findme/replaceme/g' acc/acc
由于-execdir具有一个非常令人讨厌的缺点,需要进行PATH破解:如果您的PATH环境变量中存在任何相对路径(例如./node_modules/.bin),则find会拒绝使用-execdir执行任何操作,并失败提示:

find:在PATH环境变量中包含相对路径“./node_modules/.bin”,这与find的-execdir操作组合在一起是不安全的。 请从$PATH中删除该条目。

另请参见:https://askubuntu.com/questions/621132/why-using-the-execdir-action-is-insecure-for-directory-which-is-in-the-path/1109378#1109378

-execdir是GNU find的POSIX扩展rename基于Perl并来自rename软件包。

重命名前瞻解决方法

如果您的输入路径不来自find,或者如果您已经受够了相对路径的烦恼,我们可以使用一些Perl前瞻来安全地重命名目录,如下所示:

git ls-files | sort -r | xargs rename 's/findme(?!.*\/)\/?$/replaceme/g' '{}'

我还没有找到与-execdirxargs相匹配的方便工具: https://superuser.com/questions/893890/xargs-change-working-directory-to-file-path-before-executing/915686

sort -r是必需的,以确保文件在它们各自目录的后面,因为较长的路径会在具有相同前缀的较短路径之后出现。

在Ubuntu 18.10中测试过。


5
对我来说没问题。这个解决方案应该得到更高的投票。 - Paul Chris Jones
一个改进:如果文件不匹配,则不要尝试重命名-即将“匹配”部分作为单独的参数传递,并将其作为参数用于使用-iname进行查找。 - MrR
1
很棒的答案!它还可以正确地处理目录重命名。我只想建议一种避免设置PATH=/usr/bin解决方法的方式。在调用rename时,只需给出完整路径:find . -depth -name "cnn4*" -execdir /usr/bin/rename 's/cnn4/cnn5/' '{}' \; - Helder Daniel
@HelderDaniel,“PATH”黑客是为那些在其“PATH”上具有相对路径的人提供的解决方法(可以说是一个不好的想法,但这是我的情况),请参见“node_modules”提到。有时很难决定是否应该更突出地给出“无论如何都能工作”或“在合理条件下工作”的命令版本。 - Ciro Santilli OurBigBook.com
1
@CiroSantilliПутлерКапут六四事,是的,我明白了。我只是建议另一种处理路径问题的方法。如果我们指定完整路径,就不需要担心PATH环境变量的值。但好吧,实际上是一样的。无论是在PATH变量中还是在命令之前,我们都必须指定完整路径。 - Helder Daniel

6

上述脚本可以写在一行中:

find /tmp -name "*.txt" -exec bash -c 'mv $0 $(echo "$0" | sed -r \"s|.txt|.cpp|g\")' '{}' \;

1
@Steven 试着使用这个命令: find . -name "*.txt" -exec bash -c 'mv $0 $(echo "$0" | sed -r "s/.txt/.cpp/g")' '{}' ; - xxbinxx
1
为每个文件打开/执行一个新的 shell 是非常昂贵(慢)的,所以我通常不这样做。 - Binarus

4

如果您只是想重命名文件,而不介意使用外部工具,那么您可以使用rnm。 命令如下:

#on current folder
rnm -dp -1 -fo -ssf '_dbg' -rs '/_dbg//' *

-dp -1会使其递归到所有子目录。

-fo表示仅文件模式。

-ssf '_dbg'搜索带有_dbg的文件名。

-rs '/_dbg//'将_dbg替换为空字符串。

您也可以使用当前文件夹的路径运行上述命令:

rnm -dp -1 -fo -ssf '_dbg' -rs '/_dbg//' /path/to/the/directory

超快!你救了我的一天。非常感谢!“226785个文件在26.9568秒内重命名。” - EyesBear
@EyesBear:如果你想在不看终端输出的情况下获得更好的性能,可以使用“-q”标志来加快速度。 - Jahid
这真是太棒了!感谢您的介绍! - taiyodayo

1

这个命令对我有用。请记得首先安装perl重命名包:

find -iname \*.* | grep oldname | rename -v "s/oldname/newname/g

1
您可以使用以下内容。
rename --no-act 's/\.html$/\.php/' *.html */*.html

我的重命名没有--no-act选项,这是在Ubuntu 16上的情况。你是在Mac OS X上吗? - Katastic Voyage
我也是。在Ubuntu 17.10上出现了未知选项:no-act。我不使用Mac OS。no-act是干运行选项。 - 井上智文

1

如果有人熟悉 fdrnr,命令如下:

fd -t f -x rnr '_dbg.txt' '.txt'

rnr 命令是:

rnr -f -r '_dbg.txt' '.txt' *

rnr具有能够撤销命令的优点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接