我在一个目录中有 1000 个文件,我想根据文件名将它们分类到子目录中。它们都一致地以 p-[number]_n-[number]_a-[number].[ext] 的集合结构命名。
这是一个小样本......
- p-12345_n-987_a-1254.jpg
- p-12345_n-987_a-9856.pdf
- p-12345_n-987_a-926.docx
- p-12345_n-384_a-583.pdf
- p-12345_n-384_a-987.pdf
- p-2089_n-2983_a-2348.gif
- p-2089_n-1982_a-403.jpeg
- p-38422_n-2311_a-126.pdf
- p-38422_n-2311_a-5231.docx
我想要的是这样的文件夹结构:
p-12345
⊢ n-987
⊢ p-12345_n-987_a-1254.jpg
⊢ p-12345_n-987_a-9856.pdf
⊢ p-12345_n-987_a-926.docx
⊢ n-384
⊢ p-12345_n-384_a-583.pdf
⊢ p-12345_n-384_a-987.pdf
p-2089
⊢ n-2983
⊢ p-2089_n-2983_a-2348.gif
⊢ n-1982
⊢ p-2089_n-1982_a-403.jpeg
p-38422
⊢ n-2311
⊢ p-38422_n-2311_a-126.pdf
⊢ p-38422_n-2311_a-5231.docx
我希望这是有道理的。
是否可以编写一个脚本来以这种方式组织文件?
编辑:澄清一下:是的,我的问题应该是如何我可以编写一个脚本来组织文件吗? :) 我对 Unix 和命令行都很陌生。到目前为止,我只编写/使用过基本的 shell 脚本。我有预感,答案可能会涉及正则表达式,但除此之外,我不太确定从哪里开始。
我想出的最好的主意是
- 将文件列表导出到文本文件
- 查找“_n”和“_a”并将其替换为“/n”和“/a”
- 从中创建一系列 mv 命令
- 将其保存为 shell 脚本
我确信这比实际需要的要冗长得多。我还希望有一些可重复的内容,以防将来需要对更多文件执行此操作。
答案1
当然:
#!/bin/bash
for i in p-*_n-*.*; do
Ppart=${i/_n-*}
x=${i/${Ppart}_/}
nPart=${x/_a-*}
mkdir -p $Ppart/$nPart
mv $i $Ppart/$nPart
done
首先循环遍历与您给出的模式匹配的所有文件名。在每个循环中,使用 shell 替换来删除从 部分开始的文件名的最后部分_n-
,这给出了 P 部分(第一级目录)。现在我们需要 N 部分,从n-
up 开始到_a-
部分。我分两步执行此操作:首先删除 Ppart,然后从该_a-
零件开始删除最后一个零件。
现在用于mkdir -p
创建必要的目录。mkdir -p
如果路径已经存在,则不会给出错误,因此mkdir -p
在决定执行命令之前,直接执行而不是测试目录是否存在会更容易。
最后将文件 mv 到正确的目录中。
答案2
正如已经指出的,简短的答案是“是”。
长答案是:您可以使用 bash 脚本来完成此操作,该脚本用于awk
提取您想要作为目录结构基础的文件名元素。它可能看起来像这样(其中更强调可读性而不是“一行”紧凑性)。
#!/bin/bash
for FILE in p-*
do
if [[ ! -f $FILE ]]; then continue; fi
LVL1="$(awk '{match($1,"^p-([[:digit:]]+)_[[:print:]]*",fields); print fields[1]}' <<< $FILE)"
LVL2="$(awk '{match($1,"^p-([[:digit:]]+)_n-([[:digit:]]+)_[[:print:]]*",fields); print fields[2]}' <<< $FILE)"
echo "move $FILE to p-$LVL1/n-$LVL2"
if [[ ! -d "p-$LVL1" ]]
then
mkdir "p-$LVL1"
fi
if [[ ! -d "p-$LVL1/n-$LVL2" ]]
then
mkdir "p-$LVL1/n-$LVL2"
fi
mv $FILE "p-$LVL1/n-$LVL2"
done
解释:
- 我们对当前目录中以“p-”开头的所有文件执行循环。
- 循环中的第一条指令确保文件存在,并且是空目录的解决方法(之所以需要这样做,是因为在这个论坛上,您总是会被告知不解析输出
ls
,所以类似的事情FILES=$(ls p-*); for FILE in $FILES; do ...
将被认为是不行的)。 - 然后,我们使用(正如您所怀疑的,使用正则表达式)提取生成目录结构第一级所需的
p-
和之间的数字,第二级的和之间的数字也是如此。这个想法是使用该函数,该函数不仅查找输入中出现指定正则表达式的位置,而且还为您提供数组“fields”中圆括号内的所有元素的“完整”值。_n
awk
n-
_a
match
( ... )
- 第三,我们检查您想要的目录结构的第一层和第二层的目录是否已经存在。如果没有,我们就创建它们。
- 最后,我们将文件移动到目标目录。
欲了解更多信息,请查看高级 bash 脚本编写指南和GNU Awk 用户指南。
一旦您对脚本和正则表达式更加坚定,您就可以使其变得更加紧凑;例如,在上面的脚本中,目录/子目录路径的生成可以轻松地缩减为一次awk
调用。
其一,由于目录名称是实际上
p-<number>
并且n-<number>
,与您的文件名相同,我们awk
也可以通过编写来为我们提取这些字符match($1,"(^p-[[:digit:]]+)_(n-[[:digit:]]+)_[[:print:]]*",fields)
awk
我们可以通过让它同时使用合适的参数生成目录子目录路径来进一步减轻工作量print
:
awk '{match($1,"(^p-[[:digit:]]+)_(n-[[:digit:]]+)_[[:print:]]*",fields); print fields[1] "/" fields[2]}'
很容易p-12345/n-384
为 file产生(例如) p-12345_n-384_a-583.pdf
。如果我们将其与 @wurtel 指示的用法结合起来mkdir -p
,脚本可能看起来像
for FILE in p-*
do
if [[ ! -f $FILE ]]; then continue; fi
TARGET="$(awk '{match($1,"(^p-[[:digit:]]+)_(n-[[:digit:]]+)_[[:print:]]*",fields); print fields[1] "/" fields[2]}' <<< $FILE)"
echo "move $FILE to $TARGET"
mkdir -p "$TARGET"
mv $FILE $TARGET
done
答案3
Python 的另一个版本 (3):
import os
sourcepath='/path/to/source'
destination='/path/to/destination'
(_,_,fnames) = next(os.walk(sourcepath))
for f in fnames:
subpath = '/'.join(f.split('_')[:-1])
print("Moving {} to {}".format(os.path.join(sourcepath, f), os.path.join(destination, subpath , f)))
os.makedirs(os.path.join(destination, subpath), exist_ok=True)
os.rename(os.path.join(sourcepath, f), os.path.join(destination, subpath , f))
答案4
来一首漂亮的单线怎么样
ls | awk -F"_" '{system("mkdir -p " $1 "/" $2 "&& mv " $0 " " $1 "/" $2 "/" $0)}'
根据_
创建所需的目录分隔文件名部分,然后将未更改的文件名移动到新创建的目录。