bash 使用 find 和 sed 递归重命名文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4793892/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 20:07:02  来源:igfitidea点击:

Recursively rename files using find and sed

bashscriptingsedfindreplace

提问by opsb

I want to go through a bunch of directories and rename all files that end in _test.rb to end in _spec.rb instead. It's something I've never quite figured out how to do with bash so this time I thought I'd put some effort in to get it nailed. I've so far come up short though, my best effort is:

我想浏览一堆目录并将所有以 _test.rb 结尾的文件重命名为以 _spec.rb 结尾。这是我从来没有想过如何处理 bash 的事情,所以这次我想我会付出一些努力来解决它。到目前为止,我还没有完成,但我最大的努力是:

find spec -name "*_test.rb" -exec echo mv {} `echo {} | sed s/test/spec/` \;

NB: there's an extra echo after exec so that the command is printed instead of run while I'm testing it.

注意:在 exec 之后有一个额外的回声,以便在我测试时打印命令而不是运行命令。

When I run it the output for each matched filename is:

当我运行它时,每个匹配文件名的输出是:

mv original original

i.e. the substitution by sed has been lost. What's the trick?

即 sed 的替代已丢失。有什么诀窍?

采纳答案by Fred Foo

This happens because sedreceives the string {}as input, as can be verified with:

发生这种情况是因为sed接收字符串{}作为输入,可以通过以下方式进行验证:

find . -exec echo `echo "{}" | sed 's/./foo/g'` \;

which prints foofoofor each file in the directory, recursively. The reason for this behavior is that the pipeline is executed once, by the shell, when it expands the entire command.

foofoo以递归方式为目录中的每个文件打印。这种行为的原因是管道在扩展整个命令时由 shell 执行一次。

There is no way of quoting the sedpipeline in such a way that findwill execute it for every file, since finddoesn't execute commands via the shell and has no notion of pipelines or backquotes. The GNU findutils manual explains how to perform a similar task by putting the pipeline in a separate shell script:

没有办法以对每个文件执行它sed的方式引用管道find,因为find它不通过 shell 执行命令并且没有管道或反引号的概念。GNU findutils 手册解释了如何通过将管道放在单独的 shell 脚本中来执行类似的任务:

#!/bin/sh
echo "" | sed 's/_test.rb$/_spec.rb/'

(There may be some perverse way of using sh -cand a ton of quotes to do all this in one command, but I'm not going to try.)

(可能有一些反常的使用方式sh -c和大量的引号在一个命令中完成所有这些,但我不打算尝试。)

回答by ramtam

To solve it in a way most close to the original problem would be probably using xargs "args per command line" option:

要以最接近原始问题的方式解决它,可能会使用 xargs "args per command line" 选项:

find . -name *_test.rb | sed -e "p;s/test/spec/" | xargs -n2 mv

It finds the files in the current working directory recursively, echoes the original file name (p) and then a modified name (s/test/spec/) and feeds it all to mvin pairs (xargs -n2). Beware that in this case the path itself shouldn't contain a string test.

它递归地查找当前工作目录中的文件,回显原始文件名 ( p),然后回显修改后的名称 ( s/test/spec/) 并将其全部提供给mv成对 ( xargs -n2)。请注意,在这种情况下,路径本身不应包含 string test

回答by ajreal

you might want to consider other way like

你可能想考虑其他方式

for file in $(find . -name "*_test.rb")
do 
  echo mv $file `echo $file | sed s/_test.rb$/_spec.rb/`
done

回答by csg

I find this one shorter

我觉得这个更短

find . -name '*_test.rb' -exec bash -c 'echo mv 
$ echo $SHELL
/bin/bash
$ _
${0/test.rb/spec.rb}' {} \;

回答by pvandenberk

You mention that you are using bashas your shell, in which case you don't actually need findand sedto achieve the batch renaming you're after...

你提到你正在使用bash你的外壳,在这种情况下你实际上并不需要findsed实现你所追求的批量重命名......

Assuming you are using bashas your shell:

假设您使用的bash是 shell:

$ shopt -p globstar
shopt -s globstar
$ _

... and assuming you have enabled the so-called globstarshell option:

...并假设您已启用所谓的globstarshell 选项:

$ which rename
/usr/bin/rename
$ _

... and finally assuming you have installed the renameutility (found in the util-linux-ngpackage)

...最后假设您已经安装了该rename实用程序(在util-linux-ng包中找到)

$ rename _test _spec **/*_test.rb

... then you can achieve the batch renaming in a bash one-lineras follows:

...然后您可以在bash one-liner 中实现批量重命名,如下所示:

for i in `find -name '*_test.rb'` ; do mv $i ${i%%_test.rb}_spec.rb ; done

(the globstarshell option will ensure that bash finds all matching *_test.rbfiles, no matter how deeply they are nested in the directory hierarchy... use help shoptto find out how to set the option)

globstarshell 选项将确保 bash 找到所有匹配的*_test.rb文件,无论它们在目录层次结构中嵌套多深...用于help shopt找出如何设置该选项)

回答by Wayne Conrad

You can do it without sed, if you want:

如果需要,您可以在没有 sed 的情况下执行此操作:

for i in `find -name '*_test.rb'` ; do mv $i `echo $i | sed 's/test/spec/'` ; done

${var%%suffix}strips suffixfrom the value of var.

${var%%suffix}suffix从 的值中剥离var

or, to do it using sed:

或者,使用 sed 来做到这一点:

find . -name "*_test.rb" | xargs rename s/_test/_spec/

回答by l3x

The easiest way:

最简单的方法

find . -name "*_test.rb" | xargs -P 4 rename s/_test/_spec/

The fastest way(assuming you have 4 processors):

最快的方法(假设您有 4 个处理器):

find . -name "*_test.rb" | xargs -s 1000000 rename s/_test/_spec/

If you have a large number of files to process, it is possible that the list of filenames piped to xargs would cause the resulting command line to exceed the maximum length allowed.

如果要处理大量文件,则通过管道传输到 xargs 的文件名列表可能会导致生成的命令行超出允许的最大长度。

You can check your system's limit using getconf ARG_MAX

您可以使用以下命令检查系统的限制 getconf ARG_MAX

On most linux systems you can use free -bor cat /proc/meminfoto find how much RAM you have to work with; Otherwise, use topor your systems activity monitor app.

在大多数 linux 系统上,您可以使用free -bcat /proc/meminfo查找您必须使用多少 RAM;否则,请使用top或您的系统活动监视器应用程序。

A safer way(assuming you have 1000000 bytes of ram to work with):

一种更安全的方法(假设您有 1000000 字节的 ram 可以使用):

find . -name "*.dar" -exec bash -c 'mv "
while IFS= read -r file; do
     echo "mv $file ${file%_test.rb}_spec.rb"  # remove "echo" when OK!
done < <(find -name "*_test.rb")
" "`echo \"
$ file="HELLOa_test.rbBYE_test.rb"
$ echo "${file%_test.rb}"          # remove _test.rb from the end
HELLOa_test.rbBYE
$ echo "${file%_test.rb}_spec.rb"  # remove _test.rb and append _spec.rb
HELLOa_test.rbBYE_spec.rb
\" | sed s/.dar/.zip/`"' {} \;

回答by rskengineer

Here is what worked for me when the file names had spaces in them. The example below recursively renames all .dar files to .zip files:

当文件名中有空格时,这对我有用。下面的示例递归地将所有 .dar 文件重命名为 .zip 文件:

$ tree
.
├── ab_testArb
├── a_test.rb
├── a_test.rb_test.rb
├── b_test.rb
├── c_test.hello
├── c_test.rb
└── mydir
    └── d_test.rb

$ while IFS= read -r file; do echo "mv $file ${file/_test.rb/_spec.rb}"; done < <(find -name "*_test.rb")
mv ./b_test.rb ./b_spec.rb
mv ./mydir/d_test.rb ./mydir/d_spec.rb
mv ./a_test.rb ./a_spec.rb
mv ./c_test.rb ./c_spec.rb

回答by fedorqui 'SO stop harming'

For this you don't need sed. You can perfectly get alone with a whileloop fed with the result of findthrough a process substitution.

为此,您不需要sed. 您可以完全独立于通过流程替换while的结果提供循环。find

So if you have a findexpression that selects the needed files, then use the syntax:

因此,如果您有一个find选择所需文件的表达式,请使用以下语法:

find spec -name "*_test.rb" -print0 | while read -d $'##代码##' file; do mv "$file" "`echo $file | sed s/test/spec/`"; done

This will findfiles and rename all of them striping the string _test.rbfrom the end and appending _spec.rb.

这将find文件并重命名所有这些文件,_test.rb从末尾剥离字符串并附加_spec.rb.

For this step we use Shell Parameter Expansionwhere ${var%string}removes the shortest matching pattern "string" from $var.

对于这一步,我们使用Shell Parameter Expansion从 中${var%string}删除最短的匹配模式“字符串” $var

##代码##

See an example:

看一个例子:

##代码##

回答by eldy

This is an example that should work in all cases. Works recursiveley, Need just shell, and support files names with spaces.

这是一个适用于所有情况的示例。递归工作,只需要shell,并支持带空格的文件名。

##代码##