从当前目录我有多个子目录:
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/
子目录现在为空。
如何更改脚本以重命名文件并将其保存在各自的子目录中?正如您所看到的,第一个循环将目录更改为子目录,因此不确定为什么文件最终会被发送到目录...
答案 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
使用,
代替/
作为分隔符。
然而,有更快的方法。
此处使用重命名实用程序,可在bash
和perl
的任何位置使用或轻松安装:
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 *.txt
或for f in */*.txt
或find *
或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