我在一个目录中有几千个文件,我想将它们整理到如下目录中:
由此:
└── Files
├── AAA.mkv
├── AAA.nfo
├── AAA-picture.jpg
├── BBB.mp4
├── BBB.srt
├── BBB-clip.mp4
├── CCC.avi
├── CCC.srt
├── CCC-clip.mov
└── CCC.nfo
对此:
└── Files
├── AAA
│ ├── AAA.mkv
│ ├── AAA.nfo
│ └── AAA-picture.jpg
├── BBB
│ ├── BBB.mp4
│ ├── BBB.srt
│ └── BBB-clip.mp4
└── CCC
├── CCC.avi
├── CCC.srt
├── CCC-clip.mov
└── CCC.nfo
文件名的长度和字数各不相同,有时以空格分隔,也可能用连字符分隔(除了以“-short”结尾的文件名)。它们主要是具有各种格式/容器的视频文件:mov/mpg/mkv/mp4/avi/ogg。有些带有字幕。有些带有相关元数据的文件(.nfo 或 -clip)
编辑:主要文件是视频(我想在此处绘制目录名称)。关联文件表示元数据。一些文件仅在扩展名上命名不同。基本文件名有六种其他变体,如 -clip.mp4 -clip.mov 或 -picture.jpg,我想如果对这几个文件提出一些建议,那么我就可以(希望)努力弄清楚其余文件。总之,AAA.mkv 移动到名为 AAA 的目录中。然后所有以 AAA 开头的元数据文件加入其中(即,在此示例中:AAA-picture.jpg 和 AAA.nfo)。因此,对于 AAA-picture.jpg 文件,基本名称实际上是一个子字符串。我想说,仅使用连字符作为分隔符可能相对安全……尽管完整的“-clip”或“-picture”会更安全。
我怎样才能做到这一点而不会得腕管综合症?我看了这但它有足够的不同,以致于我薄弱的脚本编写能力失效了。
谢谢。
答案1
我编写了一个小型 bash 脚本来执行此操作,并根据 OP、@dannysauer、@Arronical 和 @Scott 的评论进行了简化和改进
#!/bin/bash
for file in *
do mkdir -p "${file%%[.-]*}" 2>/dev/null
if [[ -d "${file%%[.-]*}" ]]; then
if [[ -f "$file" ]]; then
echo mv -v -- "$file" "${file%%[.-]*}"
fi
fi
done
首先运行 with echo
,然后运行 removeecho
以实际移动文件。必须从要移动文件的目录中运行该脚本。如果您愿意,这里是一行命令:
for file in *; do mkdir -p "${file%%[.-]*}"; if [[ -d "${file%%[.-]*}" ]]; then if [[ -f "$file" ]]; then echo mv -v -- "$file" "${file%%[.-]*}"; fi ; fi ; done
(再次,echo
测试后删除)
解释:
for file in *; do mkdir -p "${file%%[.-]*}"
创建一个目录,其名称为每个文件名称的第一部分(直到第一个连字符或点字符)标志-p
在这里非常重要 - 如果没有它,脚本将只移动第一个匹配的文件(感谢 Arronical指出这-p
将停止mkdir
尝试创建现有目录并对此提出抱怨)2>/dev/null
该脚本抱怨它无法创建与自身同名的目录(但仍然有效),因此我们丢弃该错误 - 在单行运行时不需要这样做if [[ -d "${file%%[.-]*}" ]]; then
如果存在该名称的目录(如果mkdir
成功)则...if [[ -f "$file" ]]
如果我们正在处理文件(而不是目录或其他东西)那么......mv -v -- "$file" "${file%%[.-]*}"
将其移动到匹配的目录中。
答案2
虽然您的问题带有 标记bash
,但对于此类任务来说,使用 会有些麻烦(以我的拙见)bash
。我建议使用 python,因为它有很多用于复杂任务的优秀函数,并且此答案提供了使用该语言的解决方案。
本质上这里发生的情况是我们使用正则表达式在多个分隔符处拆分文件名,仅获取第一部分并使用这些第一部分的唯一集合作为新目录的基本名称。
然后我们再次遍历顶级目录,并将文件排序到适当的位置。
该脚本并没有做任何特别的事情,实际上在算法分析中,由于嵌套的 for 循环,它不会表现得太好,但对于“快速而粗糙但可行的”解决方案来说,它还不错。如果你对每一行的作用感兴趣,有很多注释来解释功能
笔记,演示仅显示打印新文件名,仅用于测试目的。取消注释该os.rename()
部分以实际移动文件。
演示
bash-4.3$ # Same directory structure as in OP example
bash-4.3$ ls TESTDIR
bash-4.3$ # now run script
AAA AAA.mkv AAA.nfo AAA-picture.jpg BBB BBB-clip.mp4 BBB.mp4 BBB.srt
bash-4.3$ ./collate_files.py ./TESTDIR
/home/xieerqi/TESTDIR/AAA/AAA-picture.jpg
/home/xieerqi/TESTDIR/AAA/AAA.mkv
/home/xieerqi/TESTDIR/AAA/AAA.nfo
/home/xieerqi/TESTDIR/BBB/BBB.srt
/home/xieerqi/TESTDIR/BBB/BBB.mp4
/home/xieerqi/TESTDIR/BBB/BBB-clip.mp4
脚本本身
#!/usr/bin/env python
import re,sys,os
top_dir = os.path.realpath(sys.argv[1])
# Create list of items in directory first
# splitting names at multiple separators
dir_list = [os.path.join(top_dir,re.split("[.-]",f)[0])
for f in os.listdir(top_dir)
]
# Creating set ensures we will have unique
# directory namings
dir_set = set(dir_list)
# Make these directories first
for dir in dir_set:
if not os.path.exists(dir):
os.mkdir(dir)
# now get all files only, no directories
files_list = [f for f in os.listdir(top_dir)
if os.path.isfile(os.path.join(top_dir,f))
]
# Traverse lists of directories and files,
# check if a filename starts with directory
# that we're testing now, and if it does - move
# the file to that directory
for dir in dir_set:
id_string = os.path.basename(dir)
for f in files_list:
filename = os.path.basename(f)
if filename.startswith(id_string):
new_path = os.path.join(dir,filename)
print(new_path)
#os.rename(f,new_path)
补充笔记:
- 该脚本可以很好地适应在其他多个分隔符处拆分文件(在函数中
re.split()
):在方括号内添加(含义"[.-]"
)添加您想要的任何字符。 - 运动部分由函数执行
os.rename()
。或者,你可以import shutil
使用shutil.move()
函数。参见https://stackoverflow.com/a/8858026/3701431
答案3
在一个小型 Python 脚本中:
#!/usr/bin/env python3
import shutil
import os
import sys
dr = sys.argv[1]
for f in os.listdir(dr):
split = f.rfind("."); short = f.find("-")
if split != -1:
extension = f[split:]
newname = f[:short] if short != -1 else f[:split]
target = os.path.join(dr, newname)
if not os.path.exists(target):
os.mkdir(target)
shutil.move(os.path.join(dr, f), os.path.join(target, f))
使用方法:
将其复制到空文件中
另存为
move_into.py
使用目录作为参数运行它:
python3 /path/to/move_into.py /path/to/directory
该脚本假定所有(相关)文件都有扩展名。如果文件没有扩展名,则不会发生任何事情。如果这是个问题,请提及,可以轻松更改。
解释
- 该脚本寻找可能的扩展。
- 如果不存在,脚本将保留该文件(或目录)。
- 否则,文件将被“-”分割(如果存在),第一部分随后用于创建文件夹(如有必要)
- 如果不是,则使用文件的基本名称来命名文件夹。
随后,该文件被移动到相应的文件夹中。