我正在创建一个脚本,执行时它将查看目录并搜索所有文件,然后自动发现文件名模式,然后根据下面所述的附加逻辑移动它们。
假设我的文件夹中有以下文件:
- AAA.txt
- temp-203981.log
- temp-098723.log
- temp-123197.log
- temp-734692.log
- 测试1.sh
- 测试2.sh
- 测试3.sh
该脚本应该能够自动搜索该目录,并且应该发现有 4 个文件 (temp-XXX.log) 和 3 个文件 (testXXX.sh),其名称中具有匹配的前缀。然后,一旦找到文件数量,它就应该将其与定义的限制进行比较,比如 3。
如果与指定名称匹配的文件数量大于限制,则应将找到的文件移动到以匹配的文件名部分命名的文件夹中。
因此上面的父文件夹现在应该如下所示:
- AAA.txt
- temp.log(这将是包含 temp-734692.log、temp-123197.log、temp-098723.log、temp-203981.log 的文件夹)
- test.sh(这将是包含 test1.sh、test2.sh、test3.sh 的文件夹)
希望这是有道理的。
PS 我在这个脚本中使用 ASH,因此它需要能够在没有许多花哨的 bash 功能的情况下运行,否则这会更容易。
谢谢!
编辑:一开始清晰度就发生了变化。另外,如果我提供一个预定的分隔符(例如“&”),所有文件名都将具有该分隔符,这可能会更容易。该脚本仍然需要根据分隔符之前的文件名创建变量文件夹名称,但我认为这会让事情变得更清晰、更容易。
答案1
检查它是否有效,我将添加解释,说明它是如何工作的。我在 中测试了它dash
。
笔记:文件名不应包含空格、换行符。
#!/bin/dash
limit=1
printf "%s\n" * |
sed 's/[-0-9]*\..*$//' |
uniq -c |
awk -v lim=${limit} '$1 >= lim {print $2}' |
sort -r |
while read -r i; do
for j in "${i}"*; do
[ -f "$j" ] || continue
dir=${i}.${j#*.}
[ -d "$dir" ] || mkdir "$dir"
mv -v "$j" "$dir"
done
done
这里有一个问题 - 当文件名等于未来的目录名时的情况,例如aaa.txt
.在这种aaa.txt
情况下,文件名没有任何额外的字符,因此不会从中删除任何内容,因此,新的目录名称将是相同的,这会导致错误:
mkdir: cannot create directory ‘aaa.txt’: File exists
mv: 'aaa.txt' and 'aaa.txt' are the same file
此问题的一种解决方法是检查假定的目录名是否等于文件名,然后在未来的目录名中添加一些数字,例如aaa1.txt
.
示范
在脚本执行之前。
$ tree
.
├── aaa.txt
├── temp-098723.log
├── temp-123197.log
├── temp-203981.log
├── temp-734692.log
├── temp-new-file123.log
├── temp-new-file-2323-12.log
├── temp-new-file-342.log
├── test1.sh
├── test2.sh
└── test3.sh
0 directories, 11 files
脚本执行后: script.sh
$ tree
.
├── aaa.txt
├── temp.log
│ ├── temp-098723.log
│ ├── temp-123197.log
│ ├── temp-203981.log
│ └── temp-734692.log
├── temp-new-file.log
│ ├── temp-new-file123.log
│ ├── temp-new-file-2323-12.log
│ └── temp-new-file-342.log
└── test.sh
├── test1.sh
├── test2.sh
└── test3.sh
3 directories, 11 files
答案2
我可能误解了你在这里问的问题,但正如我所说,我认为这个问题有一些微妙之处,需要一个相对复杂的解决方案,即我不知道一个脚本可以有多简单来完成什么任务你要。例如,让我们仔细看看您的示例文件列表:
AAA.txt temp-203981.log temp-098723.log temp-123197.log temp-734692.log 测试1.sh 测试2.sh 测试3.sh
根据您的问题,您希望从此列表中提取的前缀为temp
和test
,其中aaa
被排除,因为只有一个文件作为aaa
前缀,并且您的示例阈值是三。但为什么没有te
前缀,因为有 7 个以 开头的文件te
?或者,既然您似乎想首先根据文件名后缀对文件进行分组,为什么新的子目录之一不是t.log
或temp-.log
而是temp.log
?我希望这个讨论清楚地表明,如果您确实希望您的程序自行确定潜在的前缀而不将前缀列表作为参数,那么您的问题陈述中存在一些歧义需要解决(以及一些相应的选择)需要制作)。
这是一个 Python 脚本,它使用了一个简单的特里树用于搜索满足一些约束的最长匹配前缀的数据结构(可以作为参数提供):
#!/usr/bin/env python2
# -*- coding: ascii -*-
"""
trieganize.py
Use the trie data structure to look for prefixes of filenames in a given
directory and then reorganiz those files into subdirectories based on
those prefixes.
In this script the trie data structure is just a dictionary of the
following form:
trie = {
"count": integer,
"children": dictionary,
"leaf": boolean
}
Where the dictionary keys have the following semantics.
count:
stores the number of total descendents of the given trie node
children:
stores the child trie nodes of the given node
leaf:
denotes whether this trie corresponds to the final character in a word
"""
import sys
import os
import string
def add_word_to_trie(trie, word):
"""Add a new word to the trie."""
if word:
trie["count"] += 1
if word[0] not in trie["children"]:
trie["children"][word[0]] = \
{"count": 0, "children": {}, "leaf": False}
add_word_to_trie(trie=trie["children"][word[0]], word=word[1:])
else:
trie["leaf"] = True
return(trie)
def expand_trie(trie, prefix='', words=None):
"""Given a trie, return the list of words it encodes."""
if words is None:
words = list()
if trie["leaf"]:
words.append(prefix)
for character, child in trie["children"].iteritems():
if trie["children"]:
expand_trie(trie=child, prefix=prefix+character, words=words)
return(words)
def extract_groups_from_trie(
trie, threshold=0, prefix='', groups=None,
minimum_prefix_length=0,
maximum_prefix_length=float("inf"),
prefix_charset=string.ascii_letters,
):
"""Given a trie and some prefix constraints, return a dictionary which
groups together the words in the trie based on shared prefixes which
satisfy the specified constraints.
"""
if groups is None:
groups = dict()
if trie["count"] >= threshold:
children = {
character: child
for character, child in trie["children"].iteritems()
if (
child["count"] >= threshold and
len(prefix) + 1 >= minimum_prefix_length and
len(prefix) + 1 <= maximum_prefix_length and
character in prefix_charset
)
}
if not children:
groups[prefix] = expand_trie(trie, prefix)
else:
for character, child in children.iteritems():
extract_groups_from_trie(
trie=child, threshold=threshold,
prefix=prefix+character, groups=groups
)
return(groups)
def reorganize_files(basedir, suffix_separator='.', threshold=3):
"""Takes a path to a directory and reorganizes the files in that
directory into subdirectories based on the prefixes of their
filenames."""
# Get the list of file names
filenames = os.listdir(basedir)
# Group the filenames by suffix
suffixes = {}
for filename in filenames:
basename, separator, suffix = filename.rpartition(suffix_separator)
if suffix not in suffixes:
suffixes[suffix] = []
suffixes[suffix].append(basename)
# For each suffix, search for prefixes
for suffix, basenames in suffixes.iteritems():
# Initialize a trie object
trie = {"count":0, "children": {}, "leaf": False}
# Add the filenames to the trie
for basename in basenames:
add_word_to_trie(trie, basename)
# Break the filenames up into groups based on their prefixes
groups = extract_groups_from_trie(trie, threshold)
# Organize the groups of files into subdirectories
for prefix, group in groups.iteritems():
targetdir = os.path.join(basedir, prefix + suffix_separator + suffix)
os.mkdir(targetdir)
for basename in group:
filename = basename + suffix_separator + suffix
sourcefile = os.path.join(basedir, filename)
targetfile = os.path.join(targetdir, filename)
os.rename(sourcefile, targetfile)
if __name__=="__main__":
reorganize_files(basedir=sys.argv[1])
为了演示这个 Python 脚本,我编写了一个小 shell 脚本来创建和填充测试目录:
#!/usr/bin/bash
# create-test-dir.sh
rm -rf /tmp/testdir
mkdir -p /tmp/testdir
files=(
aaa.txt
temp-203981.log
temp-098723.log
temp-123197.log
temp-734692.log
test1.sh
test2.sh
test3.sh
)
for file in ${files[@]}; do touch "/tmp/testdir/${file}"; done
我们可以运行脚本:
bash create-test-dir.sh
之后,我们的测试目录如下所示(运行tree /tmp/testdir
):
/tmp/测试目录/ |-- aaa.txt |-- temp-098723.log |-- temp-123197.log |-- temp-203981.log |-- temp-734692.log |-- test1.sh |-- test2.sh `--test3.sh 0个目录,8个文件
现在我们可以运行Python脚本:
python trieganize.py /tmp/testdir
之后文件组织如下:
/tmp/测试目录/ |-- aaa.txt |-- 温度日志 | |-- temp-098723.log | |-- temp-123197.log | |-- temp-203981.log | `--temp-734692.log `--测试.sh |-- test1.sh |-- test2.sh `--test3.sh 2个目录,8个文件
答案3
是的,bash
这会让事情变得更容易,但这里有一个 POSIX 解决方案:
#!/bin/sh
for pattern in "$@"; do
set -- "$pattern"*
if [ $# -gt 2 ]; then
for f in "$@"; do
[ -f "$f" ] || continue
ext="${f##*.}"
dest="${pattern}.${ext}"
[ -d "$dest" ] || mkdir "$dest"
mv "$f" "$dest"
done
fi
done
exit
这需要任意数量的模式,例如./script temp test
。对于每个模式,将位置参数设置为与该模式匹配的文件,并将它们移动到指定的文件夹(<pattern>.<file_extension>
如果有 3 个或更多与该模式匹配的文件)。我使用了您的示例文件并得到了预期的结果。
编辑:测试这$f
是一个常规文件,以避免移动目录等。