这是一个独特的情况,可以通过一个例子更好地解释
例 1 假设我有一个名为 的文件夹A
,文件夹中B.mkv
有C.srt
两个文件,我想将这些文件重命名为A.mkv
和A.srt
。
实际情况是,文件夹中有两个文件,名为American Sniper (2014)
和American.Sniper.2014.1080p.BluRay.x264.YIFY.mkv
,American.Sniper.2014.Subtitle.BluRay.ShaAnig.srt
想将这些文件重命名为American Sniper (2014).mkv
和American Sniper (2014).srt
示例 2
这应该是递归的,假设我有一个名为的文件夹A
,B
并且C
文件夹中有两个子文件夹A
。 B 和 C 的内容如下
应该转换为
即使我在文件夹上运行算法/脚本/命令A
它应该做的第三件事是,它应该忽略隐藏文件
例子
应该导致
最后它应该可以同时处理多个文件夹
答案1
有多种方法可以解决这个问题。请仔细阅读说明以获得最佳结果。
在这个答案中:
- Python 方法
find
+bash
方法- 使用 globstar 的仅 bash 方法
1. Python 解决方案
Python 是一种功能非常强大的系统管理语言,可以通过os.walk()
函数遍历目录树。在下面的脚本中,我们就是这样做的 - 我们查找所有文件并对每个文件进行操作,方法是确定每个文件的完整路径、文件的扩展名,然后通过os.rename()
函数重命名它。
脚本内容
#!/usr/bin/env python3
import os,sys
def get_all_files(treeroot):
for dir,subdirs,files in os.walk(treeroot):
for f in files:
if f in __file__: continue
fullpath = os.path.realpath( os.path.join(dir,f) )
extension = fullpath.split('.')[-1]
newpath = os.path.join(dir, dir + '.' + extension)
print('Move ' + fullpath + ' to ' + newpath )
# os.rename(fullpath,newpath)
def main():
top_dir="."
# If directory not given, assume cwd
if len(sys.argv) == 2: top_dir=sys.argv[1]
get_all_files(top_dir)
if __name__ == '__main__' : main()
笔记:#
非常非常重要的是,实际上重命名您需要删除的文件# os.rename(fullpath,newpath)
。
设置脚本
所有脚本的标准规则均适用: - 将其保存为add_location_name.py
最顶层目录 - 使用以下命令使之可执行chmod +x ./add_location_name.py
- 使用以下命令运行./add_location_name.py
测试脚本
下面是一个实际操作示例。我创建了一个目录,其中包含两个目录,Movie A (2016)
和Movie B (2016)
。它们里面都有两个文件。我们的脚本位于同一目录中:
$ tree
.
├── add_location_name.py
├── Movie A (2014)
│ ├── filea.mkv
│ └── fileb.srt
└── Movie B (2016)
├── filea.mkv
└── fileb.srt
因此当我们运行脚本时,我们将看到以下输出:
$ ./add_location_name.py
Move /home/xieerqi/testdir/Movie A (2014)/fileb.srt to ./Movie A (2014)/./Movie A (2014).srt
Move /home/xieerqi/testdir/Movie A (2014)/filea.mkv to ./Movie A (2014)/./Movie A (2014).mkv
Move /home/xieerqi/testdir/Movie B (2016)/fileb.srt to ./Movie B (2016)/./Movie B (2016).srt
Move /home/xieerqi/testdir/Movie B (2016)/filea.mkv to ./Movie B (2016)/./Movie B (2016).mkv
2.通过 find 命令和 -exec 标志解决
find
命令在很多方面都很有用,特别是在对多层目录树执行操作时。在这个特定情况下,我们可以使用它来过滤出所有文件,并对它们执行重命名操作。
首先我给大家一个解决方案:
find -type f -exec bash -c 'fp=$(dirname "$1");fn=$(basename "$fp");px="${1##*.}";mv "$1" "$fp"/"$fn"."$px"' sh "{}" \;
看起来又长又吓人,对吧?不过别担心,让我们来看看它是如何工作的。
解析命令
首先,您需要认识到有两件事正在发生:命令find
定位所有文件,bash
部分是实际执行重命名部分。从外部看,我们看到的是简单的命令:
find -type f -exec <COMMAND> \;
这只会查找所有文件(没有目录或符号链接),并且每当找到文件时都会调用其他命令。在本例中,我们拥有的特定命令是bash
。
那么 bash -c 'fp=$(dirname "$1");fn=$(basename "$fp");px="${1##*.}";mv "$1" "$fp"/"$fn"."$px"' sh "{}"
part 是做什么的?首先,先了解一下结构:bash -c 'command1;command2' arg0 arg1
。每当-c
使用 flag 时,第一个命令行参数arg0
将设置为$0
shell 名称,因此它无关紧要,但"{}"
很重要。这是 filename 的引号占位符,find
将作为参数传递给bash
。
在 bash 命令内部,我们提取文件路径fp=$(dirname "$1")
、目录名称fn=$(basename "$fp")
和文件扩展名或前缀px="${1##*.}"
。所有这些都运行得非常好,因为我们从最顶层目录运行命令(非常重要!)。最后,将使用所有这些变量mv "$1" "$fp"/"$fn"."$px"' sh "{}"
将原始文件重命名find
为我们构建的新文件名"$fp"/"$fn"."$px"
。
操作示例
命令的测试在与之前相同的目录上进行:
$ tree
.
├── Movie A (2014)
│ ├── filea.mkv
│ └── fileb.srt
└── Movie B (2016)
├── filea.mkv
└── fileb.srt
2 directories, 4 files
如果我们运行命令,用echo
而不是 ,mv
我们可以看到每个文件名分别被重命名。
$ find -type f -exec bash -c 'fp=$(dirname "$1");fn=$(basename "$fp");px="${1##*.}";echo "$1" "$fp"/"$fn"."$px"' sh ">
./Movie A (2014)/fileb.srt ./Movie A (2014)/Movie A (2014).srt
./Movie A (2014)/filea.mkv ./Movie A (2014)/Movie A (2014).mkv
./Movie B (2016)/fileb.srt ./Movie B (2016)/Movie B (2016).srt
./Movie B (2016)/filea.mkv ./Movie B (2016)/Movie B (2016).mkv
记住:上述命令echo
仅用于测试。使用时mv
没有输出,因此该命令是静默的。
3.更简单的方法:Bash 和 glob star
上述两种方法使用递归树遍历。由于在您的示例中目录树中只有两个级别(电影目录和文件),我们可以简化之前的 shell 命令,如下所示:
for f in */* ;do fp=$(dirname "$f"); ext="${f##*.}" ; echo "$f" "$fp"/"$fp"."$ext" ;done
再次,同样的想法-当您确定它正常工作时,请echo
替换。mv
测试结果是一样的:
$ tree
.
├── add_location_name.py
├── Movie A (2014)
│ ├── filea.mkv
│ └── fileb.srt
└── Movie B (2016)
├── filea.mkv
└── fileb.srt
2 directories, 5 files
$ for f in */* ;do fp=$(dirname "$f"); ext="${f##*.}" ; echo "$f" "$fp"/"$fp"."$ext" ;done
Movie A (2014)/filea.mkv Movie A (2014)/Movie A (2014).mkv
Movie A (2014)/fileb.srt Movie A (2014)/Movie A (2014).srt
Movie B (2016)/filea.mkv Movie B (2016)/Movie B (2016).mkv
Movie B (2016)/fileb.srt Movie B (2016)/Movie B (2016).srt
答案2
鉴于
$ tree
.
└── A
├── B
│ ├── somefile
│ ├── T.txt
│ ├── X.srt
│ └── Z.mkv
└── C
├── somefile
├── T.txt
├── W.mkv
└── Y.srt
3 directories, 8 files
然后
$ find A -type f \( -name '*.mkv' -o -name '*.srt' \) -not -name '.*' -exec sh -c '
for f; do
dir="${f%/*}" ; ext="${f##*.}" ; new="${dir##*/}"
echo mv -- "$f" "${dir}/${new}.${ext}"
done' sh {} +
mv -- A/C/Y.srt A/C/C.srt
mv -- A/C/W.mkv A/C/C.mkv
mv -- A/B/Z.mkv A/B/B.mkv
mv -- A/B/X.srt A/B/B.srt
(去除echo
后你当然它将会做你想做的事。