It's possible that I'm misunderstanding what you're asking here, but as stated I think that this question has some subtleties to it and requires a relatively sophisticated solution, i.e. I don't know how simple a script can be that will do what you want. For example, let's look carefully at your example list of files:
aaa.txt
temp-203981.log
temp-098723.log
temp-123197.log
temp-734692.log
test1.sh
test2.sh
test3.sh
According to your question, you want the prefixes extracted from this list to be temp
and test
, where aaa
is excluded since there is only one file with aaa
as a prefix and your example threshold is three. But why isn't te
a prefix, since there are 7 files that start with te
? Or, since it seems that you want to first group the files based on their filename suffixes, why is one of the new subdirectories not t.log
or temp-.log
instead of temp.log
? I hope this discussion makes clear that if you really want your program to determine the potential prefixes on its own without taking a list of prefixes as an argument, then there are some ambiguities in your question statement that need to be resolved (and some corresponding choices that need to be made).
Here is a Python script that uses a simple trie data structure to search for the longest matching prefixes satisfying a few constraints (which can be supplied as arguments):
#!/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])
In order to demonstrate this Python script, I wrote a small shell script to create and populate a test directory:
#!/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
We can run the script:
bash create-test-dir.sh
Afterwards, our test directory looks like this (run tree /tmp/testdir
):
/tmp/testdir/
|-- aaa.txt
|-- temp-098723.log
|-- temp-123197.log
|-- temp-203981.log
|-- temp-734692.log
|-- test1.sh
|-- test2.sh
`-- test3.sh
0 directories, 8 files
Now we can run the Python script:
python trieganize.py /tmp/testdir
And afterwards the files are organized as follows:
/tmp/testdir/
|-- aaa.txt
|-- temp.log
| |-- temp-098723.log
| |-- temp-123197.log
| |-- temp-203981.log
| `-- temp-734692.log
`-- test.sh
|-- test1.sh
|-- test2.sh
`-- test3.sh
2 directories, 8 files
temp-aaa-*
and threetemp-bbb-*
files, should it move the threetemp-bbb-*
files and leave alone the twotemp-aaa-*
or should it move the fivetemp-*
files? Or is it just a matter of dropping the final digits (which is likely a lot simpler)?grc-wget-errors-20171115-004503.log
andgrc-wget-errors-20171115-005503.log
it would then create a directorygrc-wget-errors
and move both of those files in. Does that make sense?log-09
,log-10
,log-11
,log-12
, do I move thelog-1*
files only or do I move all thelog-*
ones?