进入每个子目录并通过剥离前导字符来批量重命名文件

时间:2017-01-04 22:44:24

标签: bash file find rename

从当前目录我有多个子目录:

subdir1/
 001myfile001A.txt
 002myfile002A.txt
subdir2/
 001myfile001B.txt
 002myfile002B.txt

我想在myfile之前删除文件名中的每个字符,所以我最终会用

subdir1/
 myfile001A.txt
 myfile002A.txt
subdir2/
 myfile001B.txt
 myfile002B.txt

我有一些代码可以做到这一点......

#!/bin/bash
for d in `find . -type d -maxdepth 1`; do
   cd "$d"
   for f in `find . "*.txt"`; do
        mv "$f" "$(echo "$f" | sed -r 's/^.*myfile/myfile/')"
   done

done

然而,新重命名的文件最终在父目录

 myfile001A.txt
 myfile002A.txt
 myfile001B.txt
 myfile002B.txt
 subdir1/
 subdir2/

子目录现在为空。

如何更改脚本以重命名文件并将其保存在各自的子目录中?正如您所看到的,第一个循环将目录更改为子目录,因此不确定为什么文件最终会被发送到目录...

4 个答案:

答案 0 :(得分:1)

您可以在没有find和sed:

的情况下执行此操作
$ for f in */*.txt; do echo mv "$f" "${f/\/*myfile/\/myfile}"; done
mv subdir1/001myfile001A.txt subdir1/myfile001A.txt
mv subdir1/002myfile002A.txt subdir1/myfile002A.txt
mv subdir2/001myfile001B.txt subdir2/myfile001B.txt
mv subdir2/002myfile002B.txt subdir2/myfile002B.txt

如果您删除了echo,它实际上会重命名这些文件。

这使用shell parameter expansion替换斜杠和任何最多myfile的东西,只斜杠和myfile

请注意,如果存在多个子目录级别,则会中断。在这种情况下,您可以使用扩展pattern matching(已启用shopt -s extglob)和globstar shell选项(shopt -s globstar):

$ for f in **/*.txt; do echo mv "$f" "${f/\/*([!\/])myfile/\/myfile}"; done
mv subdir1/001myfile001A.txt subdir1/myfile001A.txt
mv subdir1/002myfile002A.txt subdir1/myfile002A.txt
mv subdir1/subdir3/001myfile001A.txt subdir1/subdir3/myfile001A.txt
mv subdir1/subdir3/002myfile002A.txt subdir1/subdir3/myfile002A.txt
mv subdir2/001myfile001B.txt subdir2/myfile001B.txt
mv subdir2/002myfile002B.txt subdir2/myfile002B.txt

这使用*([!\/])模式("零个或多个不是正斜杠的字符")。斜杠必须在括号表达式中进行转义,因为我们仍然在pattern扩展的 ${parameter/pattern/string} 部分内。

答案 1 :(得分:1)

稍作修改就可以解决您的问题:

#!/bin/bash
for f in `find . -maxdepth 2 -name "*.txt"`; do
  mv "$f" "$(echo "$f" | sed -r 's,[^/]+(myfile),\1,')"
done

注意:此sed使用,代替/作为分隔符。

然而,有更快的方法。

此处使用重命名实用程序,可在bashperl的任何位置使用或轻松安装:

find . -maxdepth 2 -name "*.txt" | rename 's,[^/]+(myfile),/$1,'

这里有1000个文件的测试:

for `find`; do mv        9.176s
rename                   0.099s

快100倍。

John Bollinger接受的答案速度是OP的两倍,但速度是这个rename解决方案的50倍:

for|for|mv "$f" "${f//}"   4.316s

另外,如果有一个包含太多项目的目录,那么它将不起作用。同样使用for f in *.txtfor f in */*.txtfind *rename ... subdir*/*的任何答案。另一方面,以find .开头的答案也适用于包含任意数量项目的目录。

答案 2 :(得分:1)

也许您想要使用以下命令:

rename 's#(.*/).*(myfile.*)#$1$2#' subdir*/*

您可以使用rename -n ...检查结果,而无需实际重命名任何内容。

关于您的实际问题:
外部循环中的find命令返回3(!)目录:

.
./subdir1
./subdir2

不受欢迎的.是所有文件最终都在父目录(即.)中的原因。您可以使用.选项排除-mindepth 1。 不幸的是,这是onyl文件登陆错误位置的原因,但不是唯一的问题。 由于您已经接受了其中一个答案,因此无需全部列出

答案 3 :(得分:1)

您的脚本有多个问题。首先,您的外部find命令没有达到预期的效果:它不仅输出每个子目录,还输出搜索根.,它本身就是一个目录。您可以通过手动运行命令以及其他方式来发现这一点。您并非真的需要使用find,但假设您使用它,这会更好:

for d in $(find * -maxdepth 0 -type d); do

此外,.是原始find命令的第一个结果,您的问题仍然存在。您的初始cd没有任何有意义的效果,因为您只是更改到您已经在的同一目录。内部循环中的find命令根植于那里,并且下降到两个子目录。因此,您选择重命名的每个文件的路径信息都会被sed删除,这就是结果最终位于初始工作目录(./subdir1/001myfile001A.txt - > myfile001A.txt)的原因。当您处理子目录时,它们中没有任何文件可以重命名。

但并非全部:内循环中的find命令不正确。由于您未在其前指定选项,find会将"*.txt"解释为指定第二个搜索根,此外还有.。您可能希望使用-name "*.txt"来过滤查找结果;没有它,find输出树中每个文件的名称。大概你是在抑制或忽略导致的错误信息。

但是假设您的子目录没有自己的子目录,如图所示,并且您不关心dotfiles,即使是这个更正的版本...

for f in `find . -name "*.txt"`;

...是一种非常重量级的说法......

for f in *.txt;

......甚至是......

for f in *?myfile*.txt;

......后者将避免尝试重命名名称不会发生变化的任何文件。

此外,当您只使用sed内置替换功能时,为每个文件名启动bash's进程非常浪费且昂贵:

    mv "$f" "${f/#*myfile/myfile}"

你会发现你的工作目录搞砸了。工作目录是整个shell环境的特征,因此它不会在每次循环迭代时自动重置。您需要以某种方式手动处理。 pushd / popd会这样做,就像在子shell中运行外循环体一样。

总的来说,这将解决问题:

#!/bin/bash

for d in $(find * -maxdepth 0 -type d); do
   pushd "$d"
   for f in *.txt; do
        mv "$f" "${f/#*myfile/myfile}"
   done
   popd
done