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

时间:2021-07-04 10:32:54

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.

NB:在执行exec后会有一个额外的echo,以便在我测试的时候打印命令而不是运行。

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的替代已经丢失。的诀窍是什么?

18 个解决方案

#1


31  

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

这是因为sed接收了字符串{}作为输入,可以通过以下方式验证:

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

which prints foofoo for 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 sed pipeline in such a way that find will execute it for every file, since find doesn'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不会通过shell执行命令,也没有管道或反向引用的概念,所以find将对每个文件执行它。GNU findutils手册解释了如何通过将管道放在一个单独的shell脚本中执行类似的任务:

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

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

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

#2


104  

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

要解决这个问题,最接近原始问题的方法可能是使用xargs“每个命令行args”选项:

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 mv in pairs (xargs -n2). Beware that in this case the path itself shouldn't contain a string test.

它会递归地在当前工作目录中找到文件,与原始文件名(p)相对应,然后是一个修改后的名称(s/test/spec/),并将其全部以对(xargs -n2)的形式反馈给mv。注意,在这种情况下,路径本身不应该包含字符串测试。

#3


22  

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

#4


17  

I find this one shorter

我发现这个更短。

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

#5


9  

You can do it without sed, if you want:

如果你想的话,你可以不用sed,

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

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

${var%后缀}从var值中除去后缀。

or, to do it using sed:

或者,使用sed:

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

#6


9  

You mention that you are using bash as your shell, in which case you don't actually need find and sed to achieve the batch renaming you're after...

您提到您使用bash作为您的shell,在这种情况下,您实际上不需要找到和sed来实现您所追求的批量重命名。

Assuming you are using bash as your shell:

假设您使用bash作为外壳:

$ echo $SHELL
/bin/bash
$ _

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

…假设你启用了所谓的globstar shell选项:

$ shopt -p globstar
shopt -s globstar
$ _

... and finally assuming you have installed the rename utility (found in the util-linux-ng package)

…最后假设您已经安装了rename工具(在util-linux-ng包中找到)

$ which rename
/usr/bin/rename
$ _

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

…然后您可以在bash一行程序中实现批量重命名:

$ rename _test _spec **/*_test.rb

(the globstar shell option will ensure that bash finds all matching *_test.rb files, no matter how deeply they are nested in the directory hierarchy... use help shopt to find out how to set the option)

(globstar shell选项将确保bash找到所有匹配的*_test。rb文件,不管它们在目录层次结构中嵌套得多么深……使用帮助shopt找出如何设置选项)

#7


5  

The easiest way:

最简单的方法:

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

The fastest way (assuming you have 4 processors):

最快的方式(假设你有4个处理器):

find . -name "*_test.rb" | xargs -P 4 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 -b or cat /proc/meminfo to find how much RAM you have to work with; Otherwise, use top or your systems activity monitor app.

在大多数linux系统中,您可以使用免费的-b或cat /proc/meminfo来查找您需要使用多少RAM;否则,使用top或您的系统活动监视器应用程序。

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

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

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

#8


1  

if you have Ruby (1.9+)

如果你有Ruby (1.9+)

ruby -e 'Dir["**/*._test.rb"].each{|x|test(?f,x) and File.rename(x,x.gsub(/_test/,"_spec") ) }'

#9


1  

In ramtam's answer which I like, the find portion works OK but the remainder does not if the path has spaces. I am not too familiar with sed, but I was able to modify that answer to:

在我喜欢的ramtam的回答中,查找部分可以正常工作,但如果路径有空格,其余部分不会。我不太熟悉sed,但是我可以修改这个答案:

find . -name "*_test.rb" | perl -pe 's/^((.*_)test.rb)$/"\1" "\2spec.rb"/' | xargs -n2 mv

I really needed a change like this because in my use case the final command looks more like

我确实需要这样的改变,因为在我的用例中,最终的命令看起来更像。

find . -name "olddir" | perl -pe 's/^((.*)olddir)$/"\1" "\2new directory"/' | xargs -n2 mv

#10


1  

I haven't the heart to do it all over again, but I wrote this in answer to Commandline Find Sed Exec. There the asker wanted to know how to move an entire tree, possibly excluding a directory or two, and rename all files and directories containing the string "OLD" to instead contain "NEW".

我没有心再做一遍,但我写了这封信是为了给命令行找到Sed的Exec。在那里,asker想知道如何移动整棵树,可能不包括一个目录或两个目录,并将包含“旧”字符串的所有文件和目录重命名为“NEW”。

Besides describing the how with painstaking verbosity below, this method may also be unique in that it incorporates built-in debugging. It basically doesn't do anything at all as written except compile and save to a variable all commands it believes it should do in order to perform the work requested.

除了描述如何在下面艰苦的冗长,这个方法也可能是独特的,它包含内置的调试。它基本上没有做任何事情,除了编译和保存到一个变量所有命令它认为它应该做,以执行所要求的工作。

It also explicitly avoids loops as much as possible. Besides the sed recursive search for more than one match of the pattern there is no other recursion as far as I know.

它还明确地避免了尽可能多的循环。除了sed递归搜索之外,在我所知道的模式中,没有其他的递归。

And last, this is entirely null delimited - it doesn't trip on any character in any filename except the null. I don't think you should have that.

最后,这完全是空的,它不会访问任何文件名中的任何字符,除了null。我认为你不应该那样做。

By the way, this is REALLY fast. Look:

顺便说一下,这个速度非常快。看:

% _mvnfind() { mv -n "${1}" "${2}" && cd "${2}"
> read -r SED <<SED
> :;s|${3}\(.*/[^/]*${5}\)|${4}\1|;t;:;s|\(${5}.*\)${3}|\1${4}|;t;s|^[0-9]*[\t]\(mv.*\)${5}|\1|p
> SED
> find . -name "*${3}*" -printf "%d\tmv %P ${5} %P\000" |
> sort -zg | sed -nz ${SED} | read -r ${6}
> echo <<EOF
> Prepared commands saved in variable: ${6}
> To view do: printf ${6} | tr "\000" "\n"
> To run do: sh <<EORUN
> $(printf ${6} | tr "\000" "\n")
> EORUN
> EOF
> }
% rm -rf "${UNNECESSARY:=/any/dirs/you/dont/want/moved}"
% time ( _mvnfind ${SRC=./test_tree} ${TGT=./mv_tree} \
> ${OLD=google} ${NEW=replacement_word} ${sed_sep=SsEeDd} \
> ${sh_io:=sh_io} ; printf %b\\000 "${sh_io}" | tr "\000" "\n" \
> | wc - ; echo ${sh_io} | tr "\000" "\n" |  tail -n 2 )

   <actual process time used:>
    0.06s user 0.03s system 106% cpu 0.090 total

   <output from wc:>

    Lines  Words  Bytes
    115     362   20691 -

    <output from tail:>

    mv .config/replacement_word-chrome-beta/Default/.../googlestars \
    .config/replacement_word-chrome-beta/Default/.../replacement_wordstars        

NOTE: The above function will likely require GNU versions of sed and find to properly handle the find printf and sed -z -e and :;recursive regex test;t calls. If these are not available to you the functionality can likely be duplicated with a few minor adjustments.

注意:上面的函数可能需要GNU版本的sed和find来正确处理find printf和sed -z -e和:;递归regex测试;t调用。如果这些功能无法提供给您,那么这些功能可能会被重复进行一些细微的调整。

This should do everything you wanted from start to finish with very little fuss. I did fork with sed, but I was also practicing some sed recursive branching techniques so that's why I'm here. It's kind of like getting a discount haircut at a barber school, I guess. Here's the workflow:

这应该做你想做的每件事,从开始到结束都非常小题大做。我用了sed,但我也练习了一些sed递归分支技术,这就是为什么我在这里。我想这有点像在理发学校剪个折扣。工作流程是这样的:

  • rm -rf ${UNNECESSARY}
    • I intentionally left out any functional call that might delete or destroy data of any kind. You mention that ./app might be unwanted. Delete it or move it elsewhere beforehand, or, alternatively, you could build in a \( -path PATTERN -exec rm -rf \{\} \) routine to find to do it programmatically, but that one's all yours.
    • 我故意忽略了任何可能删除或销毁数据的函数调用。你提到了这个。/app可能是不需要的。删除它或在其他地方预先移动它,或者,您可以在一个\(-path模式-exec rm -rf \{\} \)中构建一个以编程方式完成它的程序,但它是您的全部。
  • rm -rf ${不必要}我故意省略了任何可能删除或破坏任何类型数据的函数调用。你提到了这个。/app可能是不需要的。删除它或在其他地方预先移动它,或者,您可以在一个\(-path模式-exec rm -rf \{\} \)中构建一个以编程方式完成它的程序,但它是您的全部。
  • _mvnfind "${@}"
    • Declare its arguments and call the worker function. ${sh_io} is especially important in that it saves the return from the function. ${sed_sep} comes in a close second; this is an arbitrary string used to reference sed's recursion in the function. If ${sed_sep} is set to a value that could potentially be found in any of your path- or file-names acted upon... well, just don't let it be.
    • 声明它的参数并调用worker函数。${sh_io}尤其重要,因为它节省了函数的返回。${sed_sep}紧随其后;这是一个任意的字符串,用来引用sed在函数中的递归。如果${sed_sep}被设置为一个值,该值可能在您的任何路径中找到—或文件名称作用于…好吧,别让它成为现实。
  • _mvnfind“${@}”声明其参数并调用worker函数。${sh_io}尤其重要,因为它节省了函数的返回。${sed_sep}紧随其后;这是一个任意的字符串,用来引用sed在函数中的递归。如果${sed_sep}被设置为一个值,该值可能在您的任何路径中找到—或文件名称作用于…好吧,别让它成为现实。
  • mv -n $1 $2
    • The whole tree is moved from the beginning. It will save a lot of headache; believe me. The rest of what you want to do - the renaming - is simply a matter of filesystem metadata. If you were, for instance, moving this from one drive to another, or across filesystem boundaries of any kind, you're better off doing so at once with one command. It's also safer. Note the -noclobber option set for mv; as written, this function will not put ${SRC_DIR} where a ${TGT_DIR} already exists.
    • 整棵树从一开始就被移走了。这样可以省去很多麻烦;相信我。您想做的其他事情——重命名——只是一个文件系统元数据的问题。例如,如果您是将其从一个驱动器移动到另一个驱动器,或者跨越任何类型的文件系统边界,那么您最好立即使用一个命令。它也更安全。注意mv的-noclobber选项集;正如所写的那样,这个函数不会在已经存在${TGT_DIR}的地方放置${SRC_DIR}。
  • mv -n $1 2整棵树从一开始就被移动了。这样可以省去很多麻烦;相信我。您想做的其他事情——重命名——只是一个文件系统元数据的问题。例如,如果您是将其从一个驱动器移动到另一个驱动器,或者跨越任何类型的文件系统边界,那么您最好立即使用一个命令。它也更安全。注意mv的-noclobber选项集;正如所写的那样,这个函数不会在已经存在${TGT_DIR}的地方放置${SRC_DIR}。
  • read -R SED <<HEREDOC
    • I located all of sed's commands here to save on escaping hassles and read them into a variable to feed to sed below. Explanation below.
    • 我在这里找到了sed的所有命令,以避免麻烦,并将它们读入一个变量,以供下面的sed使用。下面的解释。
  • read -R SED < 我在这里设置了sed的所有命令,以避免麻烦,并将它们读入一个变量,以供下面的sed使用。下面的解释。
  • find . -name ${OLD} -printf
    • We begin the find process. With find we search only for anything that needs renaming because we already did all of the place-to-place mv operations with the function's first command. Rather than take any direct action with find, like an exec call, for instance, we instead use it to build out the command-line dynamically with -printf.
    • 我们开始寻找过程。我们只搜索任何需要重命名的东西,因为我们已经用函数的第一个命令完成了所有的位置到位置的mv操作。例如,我们不像exec调用那样采取任何直接的操作,而是使用它以-printf动态构建命令行。
  • 找到。-name ${OLD} -printf我们开始查找过程。我们只搜索任何需要重命名的东西,因为我们已经用函数的第一个命令完成了所有的位置到位置的mv操作。例如,我们不像exec调用那样采取任何直接的操作,而是使用它以-printf动态构建命令行。
  • %dir-depth :tab: 'mv '%path-to-${SRC}' '${sed_sep}'%path-again :null delimiter:'
    • After find locates the files we need it directly builds and prints out (most) of the command we'll need to process your renaming. The %dir-depth tacked onto the beginning of each line will help to ensure we're not trying to rename a file or directory in the tree with a parent object that has yet to be renamed. find uses all sorts of optimization techniques to walk your filesystem tree and it is not a sure thing that it will return the data we need in a safe-for-operations order. This is why we next...
    • 在找到我们需要的文件之后,我们需要它直接构建和打印出我们需要处理您的重命名的命令。在每一行的开头添加了% dirl -depth,这将有助于确保我们不会试图将树中的文件或目录重命名为一个尚未重命名的父对象。find使用各种优化技术来遍历您的文件系统树,它不确定它是否会返回我们需要的安全操作顺序的数据。这就是为什么我们下一个…
  • %dir-depth:选项卡:“mv”% pathto - -${SRC}' ${sed_sep}'%path-again:null分隔符:'在找到我们需要的文件后,我们需要它直接构建和打印出我们需要处理您的重命名的命令的(大部分)。在每一行的开头添加了% dirl -depth,这将有助于确保我们不会试图将树中的文件或目录重命名为一个尚未重命名的父对象。find使用各种优化技术来遍历您的文件系统树,它不确定它是否会返回我们需要的安全操作顺序的数据。这就是为什么我们下一个…
  • sort -general-numerical -zero-delimited
    • We sort all of find's output based on %directory-depth so that the paths nearest in relationship to ${SRC} are worked first. This avoids possible errors involving mving files into non-existent locations, and it minimizes need to for recursive looping. (in fact, you might be hard-pressed to find a loop at all)
    • 我们根据%directory-depth对所有的find的输出进行排序,从而使与${SRC}最接近的路径首先工作。这避免了可能的错误,包括将文件合并到不存在的位置,并且最小化了对递归循环的需求。(事实上,你可能很难找到一个循环)
  • 排序-通用数字-零分隔符,我们根据%directory-depth对所有的find的输出进行排序,以使与${SRC}最接近的路径首先工作。这避免了可能的错误,包括将文件合并到不存在的位置,并且最小化了对递归循环的需求。(事实上,你可能很难找到一个循环)
  • sed -ex :rcrs;srch|(save${sep}*til)${OLD}|\saved${SUBSTNEW}|;til ${OLD=0}
    • I think this is the only loop in the whole script, and it only loops over the second %Path printed for each string in case it contains more than one ${OLD} value that might need replacing. All other solutions I imagined involved a second sed process, and while a short loop may not be desirable, certainly it beats spawning and forking an entire process.
    • 我认为这是整个脚本中唯一的循环,它只循环遍历每个字符串的第二个%路径,以防它包含超过一个可能需要替换的${OLD}值。我想象的所有其他解决方案都涉及到第二个sed过程,虽然短循环可能不可取,但它肯定比生成整个流程的过程要好。
    • So basically what sed does here is search for ${sed_sep}, then, having found it, saves it and all characters it encounters until it finds ${OLD}, which it then replaces with ${NEW}. It then heads back to ${sed_sep} and looks again for ${OLD}, in case it occurs more than once in the string. If it is not found, it prints the modified string to stdout (which it then catches again next) and ends the loop.
    • 因此,sed在这里所做的基本上就是搜索${sed_sep},然后找到它,保存它和它遇到的所有字符,直到它找到${OLD},然后用${NEW}替换它。然后返回${sed_sep},并再次查看${OLD},以防它在字符串中不止一次出现。如果未找到,则将修改后的字符串打印到stdout(然后再次捕获)并结束循环。
    • This avoids having to parse the entire string, and ensures that the first half of the mv command string, which needs to include ${OLD} of course, does include it, and the second half is altered as many times as is necessary to wipe the ${OLD} name from mv's destination path.
    • 这避免了必须解析整个字符串,并确保mv命令字符串的前一半(当然需要包含${OLD})包含它,而后半部分被修改了很多次,以从mv的目标路径中清除${OLD}名。
  • sed -ex:rcrs;srch|(节省${sep}*til)${旧}|\保存${SUBSTNEW}|;til ${OLD=0},我认为这是整个脚本中唯一的循环,它只循环遍历每个字符串的第二个%路径,以防它包含超过一个${OLD}值,可能需要替换。我想象的所有其他解决方案都涉及到第二个sed过程,虽然短循环可能不可取,但它肯定比生成整个流程的过程要好。因此,sed在这里所做的基本上就是搜索${sed_sep},然后找到它,保存它和它遇到的所有字符,直到它找到${OLD},然后用${NEW}替换它。然后返回${sed_sep},并再次查看${OLD},以防它在字符串中不止一次出现。如果未找到,则将修改后的字符串打印到stdout(然后再次捕获)并结束循环。这避免了必须解析整个字符串,并确保mv命令字符串的前一半(当然需要包含${OLD})包含它,而后半部分被修改了很多次,以从mv的目标路径中清除${OLD}名。
  • sed -ex...-ex search|%dir_depth(save*)${sed_sep}|(only_saved)|out
    • The two -exec calls here happen without a second fork. In the first, as we've seen, we modify the mv command as supplied by find's -printf function command as necessary to properly alter all references of ${OLD} to ${NEW}, but in order to do so we had to use some arbitrary reference points which should not be included in the final output. So once sed finishes all it needs to do, we instruct it to wipe out its reference points from the hold-buffer before passing it along.
    • 这里的两个exec调用没有第二个fork。首先,正如我们所看到的,我们修改提供的mv命令找到printf函数命令需要适当改变的所有引用$ {老} $ { },但是为了这样做,我们不得不使用任意参考点,不应包含在最终的输出。所以一旦sed完成了它所需要做的,我们指示它在传递它之前将它的参考点从hold缓冲区中删除。
  • sed的前女友……-ex搜索|%dir_depth(save*)${sed_sep}|(only_saved)|输出两个-exec调用在没有第二个fork的情况下发生。首先,正如我们所看到的,我们修改提供的mv命令找到printf函数命令需要适当改变的所有引用$ {老} $ { },但是为了这样做,我们不得不使用任意参考点,不应包含在最终的输出。所以一旦sed完成了它所需要做的,我们指示它在传递它之前将它的参考点从hold缓冲区中删除。

AND NOW WE'RE BACK AROUND

现在我们又回来了。

read will receive a command that looks like this:

read将接收如下命令:

% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000

It will read it into ${msg} as ${sh_io} which can be examined at will outside of the function.

它将把它读入${msg}作为${sh_io},它可以在函数之外进行检查。

Cool.

酷。

-Mike

迈克

#11


1  

I was able handle filenames with spaces by following the examples suggested by onitake.

根据onitake的建议,我可以使用空格来处理文件名。

This doesn't break if the path contains spaces or the string test:

如果路径包含空格或字符串测试,则不会中断:

find . -name "*_test.rb" -print0 | while read -d $'\0' file
do
    echo mv "$file" "$(echo $file | sed s/test/spec/)"
done

#12


1  

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

这是一个在所有情况下都应该适用的例子。工作递归,只需shell,并支持文件名称空间。

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

#13


1  

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文件:

find . -name "*.dar" -exec bash -c 'mv "$0" "`echo \"$0\" | sed s/.dar/.zip/`"' {} \;

#14


1  

For this you don't need sed. You can perfectly get alone with a while loop fed with the result of find through a process substitution.

为了这个,你不需要sed。您完全可以单独使用一个while循环,并通过一个过程替换找到结果。

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

因此,如果您有一个查找表达式来选择所需的文件,那么可以使用语法:

while IFS= read -r file; do
     echo "mv $file ${file%_test.rb}_spec.rb"  # remove "echo" when OK!
done < <(find -name "*_test.rb")

This will find files and rename all of them striping the string _test.rb from the end and appending _spec.rb.

这将找到文件并将它们重命名为striping字符串_test。从末尾的rb并附加_spec.rb。

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

在此步骤中,我们使用Shell参数扩展,其中${var%string}从$var中移除最短匹配模式“string”。

$ 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

See an example:

看一个例子:

$ 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

#15


0  

$ find spec -name "*_test.rb"
spec/dir2/a_test.rb
spec/dir1/a_test.rb

$ find spec -name "*_test.rb" | xargs -n 1 /usr/bin/perl -e '($new=$ARGV[0]) =~ s/test/spec/; system(qq(mv),qq(-v), $ARGV[0], $new);'
`spec/dir2/a_test.rb' -> `spec/dir2/a_spec.rb'
`spec/dir1/a_test.rb' -> `spec/dir1/a_spec.rb'

$ find spec -name "*_spec.rb"
spec/dir2/b_spec.rb
spec/dir2/a_spec.rb
spec/dir1/a_spec.rb
spec/dir1/c_spec.rb

#16


0  

Your question seems to be about sed, but to accomplish your goal of recursive rename, I'd suggest the following, shamelessly ripped from another answer I gave here:recursive rename in bash

您的问题似乎是关于sed的,但是为了实现您的递归重命名的目标,我建议您从我在这里给出的另一个答案中(在bash中使用递归的重命名)来消除这个问题。

#!/bin/bash
IFS=$'\n'
function RecurseDirs
{
for f in "$@"
do
  newf=echo "${f}" | sed -e 's/^(.*_)test.rb$/\1spec.rb/g'
    echo "${f}" "${newf}"
    mv "${f}" "${newf}"
    f="${newf}"
  if [[ -d "${f}" ]]; then
    cd "${f}"
    RecurseDirs $(ls -1 ".")
  fi
done
cd ..
}
RecurseDirs .

#17


0  

More secure way of doing rename with find utils and sed regular expression type:

使用find utils和sed正则表达式类型进行重命名更安全的方法:

  mkdir ~/practice

  cd ~/practice

  touch classic.txt.txt

  touch folk.txt.txt

Remove the ".txt.txt" extension as follows -

删除" . txt。扩展如下-。

  cd ~/practice

  find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} \;

If you use the + in place of ; in order to work on batch mode, the above command will rename only the first matching file, but not the entire list of file matches by 'find'.

如果你用+代替;为了在批处理模式下工作,上面的命令将只重命名第一个匹配文件,而不是通过'find'来重命名文件匹配的整个列表。

  find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} +

#18


0  

Here's a nice oneliner that does the trick. Sed can't handle this right, especially if multiple variables are passed by xargs with -n 2. A bash substition would handle this easily like:

这是一个很好的oneliner。Sed无法处理这个问题,尤其是当xargs与-n 2传递多个变量时。bash substition可以很容易地处理这个问题:

find ./spec -type f -name "*_test.rb" -print0 | xargs -0 -I {} sh -c 'export file={}; mv $file ${file/_test.rb/_spec.rb}'

Adding -type -f will limit the move operations to files only, -print 0 will handle empty spaces in paths.

添加-type -f将只将移动操作限制为文件,-print 0将处理路径中的空白空间。

#1


31  

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

这是因为sed接收了字符串{}作为输入,可以通过以下方式验证:

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

which prints foofoo for 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 sed pipeline in such a way that find will execute it for every file, since find doesn'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不会通过shell执行命令,也没有管道或反向引用的概念,所以find将对每个文件执行它。GNU findutils手册解释了如何通过将管道放在一个单独的shell脚本中执行类似的任务:

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

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

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

#2


104  

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

要解决这个问题,最接近原始问题的方法可能是使用xargs“每个命令行args”选项:

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 mv in pairs (xargs -n2). Beware that in this case the path itself shouldn't contain a string test.

它会递归地在当前工作目录中找到文件,与原始文件名(p)相对应,然后是一个修改后的名称(s/test/spec/),并将其全部以对(xargs -n2)的形式反馈给mv。注意,在这种情况下,路径本身不应该包含字符串测试。

#3


22  

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

#4


17  

I find this one shorter

我发现这个更短。

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

#5


9  

You can do it without sed, if you want:

如果你想的话,你可以不用sed,

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

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

${var%后缀}从var值中除去后缀。

or, to do it using sed:

或者,使用sed:

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

#6


9  

You mention that you are using bash as your shell, in which case you don't actually need find and sed to achieve the batch renaming you're after...

您提到您使用bash作为您的shell,在这种情况下,您实际上不需要找到和sed来实现您所追求的批量重命名。

Assuming you are using bash as your shell:

假设您使用bash作为外壳:

$ echo $SHELL
/bin/bash
$ _

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

…假设你启用了所谓的globstar shell选项:

$ shopt -p globstar
shopt -s globstar
$ _

... and finally assuming you have installed the rename utility (found in the util-linux-ng package)

…最后假设您已经安装了rename工具(在util-linux-ng包中找到)

$ which rename
/usr/bin/rename
$ _

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

…然后您可以在bash一行程序中实现批量重命名:

$ rename _test _spec **/*_test.rb

(the globstar shell option will ensure that bash finds all matching *_test.rb files, no matter how deeply they are nested in the directory hierarchy... use help shopt to find out how to set the option)

(globstar shell选项将确保bash找到所有匹配的*_test。rb文件,不管它们在目录层次结构中嵌套得多么深……使用帮助shopt找出如何设置选项)

#7


5  

The easiest way:

最简单的方法:

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

The fastest way (assuming you have 4 processors):

最快的方式(假设你有4个处理器):

find . -name "*_test.rb" | xargs -P 4 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 -b or cat /proc/meminfo to find how much RAM you have to work with; Otherwise, use top or your systems activity monitor app.

在大多数linux系统中,您可以使用免费的-b或cat /proc/meminfo来查找您需要使用多少RAM;否则,使用top或您的系统活动监视器应用程序。

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

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

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

#8


1  

if you have Ruby (1.9+)

如果你有Ruby (1.9+)

ruby -e 'Dir["**/*._test.rb"].each{|x|test(?f,x) and File.rename(x,x.gsub(/_test/,"_spec") ) }'

#9


1  

In ramtam's answer which I like, the find portion works OK but the remainder does not if the path has spaces. I am not too familiar with sed, but I was able to modify that answer to:

在我喜欢的ramtam的回答中,查找部分可以正常工作,但如果路径有空格,其余部分不会。我不太熟悉sed,但是我可以修改这个答案:

find . -name "*_test.rb" | perl -pe 's/^((.*_)test.rb)$/"\1" "\2spec.rb"/' | xargs -n2 mv

I really needed a change like this because in my use case the final command looks more like

我确实需要这样的改变,因为在我的用例中,最终的命令看起来更像。

find . -name "olddir" | perl -pe 's/^((.*)olddir)$/"\1" "\2new directory"/' | xargs -n2 mv

#10


1  

I haven't the heart to do it all over again, but I wrote this in answer to Commandline Find Sed Exec. There the asker wanted to know how to move an entire tree, possibly excluding a directory or two, and rename all files and directories containing the string "OLD" to instead contain "NEW".

我没有心再做一遍,但我写了这封信是为了给命令行找到Sed的Exec。在那里,asker想知道如何移动整棵树,可能不包括一个目录或两个目录,并将包含“旧”字符串的所有文件和目录重命名为“NEW”。

Besides describing the how with painstaking verbosity below, this method may also be unique in that it incorporates built-in debugging. It basically doesn't do anything at all as written except compile and save to a variable all commands it believes it should do in order to perform the work requested.

除了描述如何在下面艰苦的冗长,这个方法也可能是独特的,它包含内置的调试。它基本上没有做任何事情,除了编译和保存到一个变量所有命令它认为它应该做,以执行所要求的工作。

It also explicitly avoids loops as much as possible. Besides the sed recursive search for more than one match of the pattern there is no other recursion as far as I know.

它还明确地避免了尽可能多的循环。除了sed递归搜索之外,在我所知道的模式中,没有其他的递归。

And last, this is entirely null delimited - it doesn't trip on any character in any filename except the null. I don't think you should have that.

最后,这完全是空的,它不会访问任何文件名中的任何字符,除了null。我认为你不应该那样做。

By the way, this is REALLY fast. Look:

顺便说一下,这个速度非常快。看:

% _mvnfind() { mv -n "${1}" "${2}" && cd "${2}"
> read -r SED <<SED
> :;s|${3}\(.*/[^/]*${5}\)|${4}\1|;t;:;s|\(${5}.*\)${3}|\1${4}|;t;s|^[0-9]*[\t]\(mv.*\)${5}|\1|p
> SED
> find . -name "*${3}*" -printf "%d\tmv %P ${5} %P\000" |
> sort -zg | sed -nz ${SED} | read -r ${6}
> echo <<EOF
> Prepared commands saved in variable: ${6}
> To view do: printf ${6} | tr "\000" "\n"
> To run do: sh <<EORUN
> $(printf ${6} | tr "\000" "\n")
> EORUN
> EOF
> }
% rm -rf "${UNNECESSARY:=/any/dirs/you/dont/want/moved}"
% time ( _mvnfind ${SRC=./test_tree} ${TGT=./mv_tree} \
> ${OLD=google} ${NEW=replacement_word} ${sed_sep=SsEeDd} \
> ${sh_io:=sh_io} ; printf %b\\000 "${sh_io}" | tr "\000" "\n" \
> | wc - ; echo ${sh_io} | tr "\000" "\n" |  tail -n 2 )

   <actual process time used:>
    0.06s user 0.03s system 106% cpu 0.090 total

   <output from wc:>

    Lines  Words  Bytes
    115     362   20691 -

    <output from tail:>

    mv .config/replacement_word-chrome-beta/Default/.../googlestars \
    .config/replacement_word-chrome-beta/Default/.../replacement_wordstars        

NOTE: The above function will likely require GNU versions of sed and find to properly handle the find printf and sed -z -e and :;recursive regex test;t calls. If these are not available to you the functionality can likely be duplicated with a few minor adjustments.

注意:上面的函数可能需要GNU版本的sed和find来正确处理find printf和sed -z -e和:;递归regex测试;t调用。如果这些功能无法提供给您,那么这些功能可能会被重复进行一些细微的调整。

This should do everything you wanted from start to finish with very little fuss. I did fork with sed, but I was also practicing some sed recursive branching techniques so that's why I'm here. It's kind of like getting a discount haircut at a barber school, I guess. Here's the workflow:

这应该做你想做的每件事,从开始到结束都非常小题大做。我用了sed,但我也练习了一些sed递归分支技术,这就是为什么我在这里。我想这有点像在理发学校剪个折扣。工作流程是这样的:

  • rm -rf ${UNNECESSARY}
    • I intentionally left out any functional call that might delete or destroy data of any kind. You mention that ./app might be unwanted. Delete it or move it elsewhere beforehand, or, alternatively, you could build in a \( -path PATTERN -exec rm -rf \{\} \) routine to find to do it programmatically, but that one's all yours.
    • 我故意忽略了任何可能删除或销毁数据的函数调用。你提到了这个。/app可能是不需要的。删除它或在其他地方预先移动它,或者,您可以在一个\(-path模式-exec rm -rf \{\} \)中构建一个以编程方式完成它的程序,但它是您的全部。
  • rm -rf ${不必要}我故意省略了任何可能删除或破坏任何类型数据的函数调用。你提到了这个。/app可能是不需要的。删除它或在其他地方预先移动它,或者,您可以在一个\(-path模式-exec rm -rf \{\} \)中构建一个以编程方式完成它的程序,但它是您的全部。
  • _mvnfind "${@}"
    • Declare its arguments and call the worker function. ${sh_io} is especially important in that it saves the return from the function. ${sed_sep} comes in a close second; this is an arbitrary string used to reference sed's recursion in the function. If ${sed_sep} is set to a value that could potentially be found in any of your path- or file-names acted upon... well, just don't let it be.
    • 声明它的参数并调用worker函数。${sh_io}尤其重要,因为它节省了函数的返回。${sed_sep}紧随其后;这是一个任意的字符串,用来引用sed在函数中的递归。如果${sed_sep}被设置为一个值,该值可能在您的任何路径中找到—或文件名称作用于…好吧,别让它成为现实。
  • _mvnfind“${@}”声明其参数并调用worker函数。${sh_io}尤其重要,因为它节省了函数的返回。${sed_sep}紧随其后;这是一个任意的字符串,用来引用sed在函数中的递归。如果${sed_sep}被设置为一个值,该值可能在您的任何路径中找到—或文件名称作用于…好吧,别让它成为现实。
  • mv -n $1 $2
    • The whole tree is moved from the beginning. It will save a lot of headache; believe me. The rest of what you want to do - the renaming - is simply a matter of filesystem metadata. If you were, for instance, moving this from one drive to another, or across filesystem boundaries of any kind, you're better off doing so at once with one command. It's also safer. Note the -noclobber option set for mv; as written, this function will not put ${SRC_DIR} where a ${TGT_DIR} already exists.
    • 整棵树从一开始就被移走了。这样可以省去很多麻烦;相信我。您想做的其他事情——重命名——只是一个文件系统元数据的问题。例如,如果您是将其从一个驱动器移动到另一个驱动器,或者跨越任何类型的文件系统边界,那么您最好立即使用一个命令。它也更安全。注意mv的-noclobber选项集;正如所写的那样,这个函数不会在已经存在${TGT_DIR}的地方放置${SRC_DIR}。
  • mv -n $1 2整棵树从一开始就被移动了。这样可以省去很多麻烦;相信我。您想做的其他事情——重命名——只是一个文件系统元数据的问题。例如,如果您是将其从一个驱动器移动到另一个驱动器,或者跨越任何类型的文件系统边界,那么您最好立即使用一个命令。它也更安全。注意mv的-noclobber选项集;正如所写的那样,这个函数不会在已经存在${TGT_DIR}的地方放置${SRC_DIR}。
  • read -R SED <<HEREDOC
    • I located all of sed's commands here to save on escaping hassles and read them into a variable to feed to sed below. Explanation below.
    • 我在这里找到了sed的所有命令,以避免麻烦,并将它们读入一个变量,以供下面的sed使用。下面的解释。
  • read -R SED < 我在这里设置了sed的所有命令,以避免麻烦,并将它们读入一个变量,以供下面的sed使用。下面的解释。
  • find . -name ${OLD} -printf
    • We begin the find process. With find we search only for anything that needs renaming because we already did all of the place-to-place mv operations with the function's first command. Rather than take any direct action with find, like an exec call, for instance, we instead use it to build out the command-line dynamically with -printf.
    • 我们开始寻找过程。我们只搜索任何需要重命名的东西,因为我们已经用函数的第一个命令完成了所有的位置到位置的mv操作。例如,我们不像exec调用那样采取任何直接的操作,而是使用它以-printf动态构建命令行。
  • 找到。-name ${OLD} -printf我们开始查找过程。我们只搜索任何需要重命名的东西,因为我们已经用函数的第一个命令完成了所有的位置到位置的mv操作。例如,我们不像exec调用那样采取任何直接的操作,而是使用它以-printf动态构建命令行。
  • %dir-depth :tab: 'mv '%path-to-${SRC}' '${sed_sep}'%path-again :null delimiter:'
    • After find locates the files we need it directly builds and prints out (most) of the command we'll need to process your renaming. The %dir-depth tacked onto the beginning of each line will help to ensure we're not trying to rename a file or directory in the tree with a parent object that has yet to be renamed. find uses all sorts of optimization techniques to walk your filesystem tree and it is not a sure thing that it will return the data we need in a safe-for-operations order. This is why we next...
    • 在找到我们需要的文件之后,我们需要它直接构建和打印出我们需要处理您的重命名的命令。在每一行的开头添加了% dirl -depth,这将有助于确保我们不会试图将树中的文件或目录重命名为一个尚未重命名的父对象。find使用各种优化技术来遍历您的文件系统树,它不确定它是否会返回我们需要的安全操作顺序的数据。这就是为什么我们下一个…
  • %dir-depth:选项卡:“mv”% pathto - -${SRC}' ${sed_sep}'%path-again:null分隔符:'在找到我们需要的文件后,我们需要它直接构建和打印出我们需要处理您的重命名的命令的(大部分)。在每一行的开头添加了% dirl -depth,这将有助于确保我们不会试图将树中的文件或目录重命名为一个尚未重命名的父对象。find使用各种优化技术来遍历您的文件系统树,它不确定它是否会返回我们需要的安全操作顺序的数据。这就是为什么我们下一个…
  • sort -general-numerical -zero-delimited
    • We sort all of find's output based on %directory-depth so that the paths nearest in relationship to ${SRC} are worked first. This avoids possible errors involving mving files into non-existent locations, and it minimizes need to for recursive looping. (in fact, you might be hard-pressed to find a loop at all)
    • 我们根据%directory-depth对所有的find的输出进行排序,从而使与${SRC}最接近的路径首先工作。这避免了可能的错误,包括将文件合并到不存在的位置,并且最小化了对递归循环的需求。(事实上,你可能很难找到一个循环)
  • 排序-通用数字-零分隔符,我们根据%directory-depth对所有的find的输出进行排序,以使与${SRC}最接近的路径首先工作。这避免了可能的错误,包括将文件合并到不存在的位置,并且最小化了对递归循环的需求。(事实上,你可能很难找到一个循环)
  • sed -ex :rcrs;srch|(save${sep}*til)${OLD}|\saved${SUBSTNEW}|;til ${OLD=0}
    • I think this is the only loop in the whole script, and it only loops over the second %Path printed for each string in case it contains more than one ${OLD} value that might need replacing. All other solutions I imagined involved a second sed process, and while a short loop may not be desirable, certainly it beats spawning and forking an entire process.
    • 我认为这是整个脚本中唯一的循环,它只循环遍历每个字符串的第二个%路径,以防它包含超过一个可能需要替换的${OLD}值。我想象的所有其他解决方案都涉及到第二个sed过程,虽然短循环可能不可取,但它肯定比生成整个流程的过程要好。
    • So basically what sed does here is search for ${sed_sep}, then, having found it, saves it and all characters it encounters until it finds ${OLD}, which it then replaces with ${NEW}. It then heads back to ${sed_sep} and looks again for ${OLD}, in case it occurs more than once in the string. If it is not found, it prints the modified string to stdout (which it then catches again next) and ends the loop.
    • 因此,sed在这里所做的基本上就是搜索${sed_sep},然后找到它,保存它和它遇到的所有字符,直到它找到${OLD},然后用${NEW}替换它。然后返回${sed_sep},并再次查看${OLD},以防它在字符串中不止一次出现。如果未找到,则将修改后的字符串打印到stdout(然后再次捕获)并结束循环。
    • This avoids having to parse the entire string, and ensures that the first half of the mv command string, which needs to include ${OLD} of course, does include it, and the second half is altered as many times as is necessary to wipe the ${OLD} name from mv's destination path.
    • 这避免了必须解析整个字符串,并确保mv命令字符串的前一半(当然需要包含${OLD})包含它,而后半部分被修改了很多次,以从mv的目标路径中清除${OLD}名。
  • sed -ex:rcrs;srch|(节省${sep}*til)${旧}|\保存${SUBSTNEW}|;til ${OLD=0},我认为这是整个脚本中唯一的循环,它只循环遍历每个字符串的第二个%路径,以防它包含超过一个${OLD}值,可能需要替换。我想象的所有其他解决方案都涉及到第二个sed过程,虽然短循环可能不可取,但它肯定比生成整个流程的过程要好。因此,sed在这里所做的基本上就是搜索${sed_sep},然后找到它,保存它和它遇到的所有字符,直到它找到${OLD},然后用${NEW}替换它。然后返回${sed_sep},并再次查看${OLD},以防它在字符串中不止一次出现。如果未找到,则将修改后的字符串打印到stdout(然后再次捕获)并结束循环。这避免了必须解析整个字符串,并确保mv命令字符串的前一半(当然需要包含${OLD})包含它,而后半部分被修改了很多次,以从mv的目标路径中清除${OLD}名。
  • sed -ex...-ex search|%dir_depth(save*)${sed_sep}|(only_saved)|out
    • The two -exec calls here happen without a second fork. In the first, as we've seen, we modify the mv command as supplied by find's -printf function command as necessary to properly alter all references of ${OLD} to ${NEW}, but in order to do so we had to use some arbitrary reference points which should not be included in the final output. So once sed finishes all it needs to do, we instruct it to wipe out its reference points from the hold-buffer before passing it along.
    • 这里的两个exec调用没有第二个fork。首先,正如我们所看到的,我们修改提供的mv命令找到printf函数命令需要适当改变的所有引用$ {老} $ { },但是为了这样做,我们不得不使用任意参考点,不应包含在最终的输出。所以一旦sed完成了它所需要做的,我们指示它在传递它之前将它的参考点从hold缓冲区中删除。
  • sed的前女友……-ex搜索|%dir_depth(save*)${sed_sep}|(only_saved)|输出两个-exec调用在没有第二个fork的情况下发生。首先,正如我们所看到的,我们修改提供的mv命令找到printf函数命令需要适当改变的所有引用$ {老} $ { },但是为了这样做,我们不得不使用任意参考点,不应包含在最终的输出。所以一旦sed完成了它所需要做的,我们指示它在传递它之前将它的参考点从hold缓冲区中删除。

AND NOW WE'RE BACK AROUND

现在我们又回来了。

read will receive a command that looks like this:

read将接收如下命令:

% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000

It will read it into ${msg} as ${sh_io} which can be examined at will outside of the function.

它将把它读入${msg}作为${sh_io},它可以在函数之外进行检查。

Cool.

酷。

-Mike

迈克

#11


1  

I was able handle filenames with spaces by following the examples suggested by onitake.

根据onitake的建议,我可以使用空格来处理文件名。

This doesn't break if the path contains spaces or the string test:

如果路径包含空格或字符串测试,则不会中断:

find . -name "*_test.rb" -print0 | while read -d $'\0' file
do
    echo mv "$file" "$(echo $file | sed s/test/spec/)"
done

#12


1  

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

这是一个在所有情况下都应该适用的例子。工作递归,只需shell,并支持文件名称空间。

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

#13


1  

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文件:

find . -name "*.dar" -exec bash -c 'mv "$0" "`echo \"$0\" | sed s/.dar/.zip/`"' {} \;

#14


1  

For this you don't need sed. You can perfectly get alone with a while loop fed with the result of find through a process substitution.

为了这个,你不需要sed。您完全可以单独使用一个while循环,并通过一个过程替换找到结果。

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

因此,如果您有一个查找表达式来选择所需的文件,那么可以使用语法:

while IFS= read -r file; do
     echo "mv $file ${file%_test.rb}_spec.rb"  # remove "echo" when OK!
done < <(find -name "*_test.rb")

This will find files and rename all of them striping the string _test.rb from the end and appending _spec.rb.

这将找到文件并将它们重命名为striping字符串_test。从末尾的rb并附加_spec.rb。

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

在此步骤中,我们使用Shell参数扩展,其中${var%string}从$var中移除最短匹配模式“string”。

$ 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

See an example:

看一个例子:

$ 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

#15


0  

$ find spec -name "*_test.rb"
spec/dir2/a_test.rb
spec/dir1/a_test.rb

$ find spec -name "*_test.rb" | xargs -n 1 /usr/bin/perl -e '($new=$ARGV[0]) =~ s/test/spec/; system(qq(mv),qq(-v), $ARGV[0], $new);'
`spec/dir2/a_test.rb' -> `spec/dir2/a_spec.rb'
`spec/dir1/a_test.rb' -> `spec/dir1/a_spec.rb'

$ find spec -name "*_spec.rb"
spec/dir2/b_spec.rb
spec/dir2/a_spec.rb
spec/dir1/a_spec.rb
spec/dir1/c_spec.rb

#16


0  

Your question seems to be about sed, but to accomplish your goal of recursive rename, I'd suggest the following, shamelessly ripped from another answer I gave here:recursive rename in bash

您的问题似乎是关于sed的,但是为了实现您的递归重命名的目标,我建议您从我在这里给出的另一个答案中(在bash中使用递归的重命名)来消除这个问题。

#!/bin/bash
IFS=$'\n'
function RecurseDirs
{
for f in "$@"
do
  newf=echo "${f}" | sed -e 's/^(.*_)test.rb$/\1spec.rb/g'
    echo "${f}" "${newf}"
    mv "${f}" "${newf}"
    f="${newf}"
  if [[ -d "${f}" ]]; then
    cd "${f}"
    RecurseDirs $(ls -1 ".")
  fi
done
cd ..
}
RecurseDirs .

#17


0  

More secure way of doing rename with find utils and sed regular expression type:

使用find utils和sed正则表达式类型进行重命名更安全的方法:

  mkdir ~/practice

  cd ~/practice

  touch classic.txt.txt

  touch folk.txt.txt

Remove the ".txt.txt" extension as follows -

删除" . txt。扩展如下-。

  cd ~/practice

  find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} \;

If you use the + in place of ; in order to work on batch mode, the above command will rename only the first matching file, but not the entire list of file matches by 'find'.

如果你用+代替;为了在批处理模式下工作,上面的命令将只重命名第一个匹配文件,而不是通过'find'来重命名文件匹配的整个列表。

  find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} +

#18


0  

Here's a nice oneliner that does the trick. Sed can't handle this right, especially if multiple variables are passed by xargs with -n 2. A bash substition would handle this easily like:

这是一个很好的oneliner。Sed无法处理这个问题,尤其是当xargs与-n 2传递多个变量时。bash substition可以很容易地处理这个问题:

find ./spec -type f -name "*_test.rb" -print0 | xargs -0 -I {} sh -c 'export file={}; mv $file ${file/_test.rb/_spec.rb}'

Adding -type -f will limit the move operations to files only, -print 0 will handle empty spaces in paths.

添加-type -f将只将移动操作限制为文件,-print 0将处理路径中的空白空间。