I wrote a program to rename files with consecutive numbers, basically removing the gaps in the numbering. This could be a very annoying task to do it manually in windows. I only did this for practicing Python purposes.
Lets show what the program does in an example:
Say you have a folder E:\Spam
which contains the following files:
eggs010.txt
spam00.txt
spam002.txt
spam004.txt
spam005.txt
spam5.dat
spam7.txt
spam009.txt
spam037.txt
spam045.txt
spam089.txt
spam2000.txt
Lets say for what reason you want to continues numbe all the files from type
spam.txt
After running my program it gives out this:
eggs010.txt (unchanged wrong filename)
spam0001.txt
spam0002.txt
spam0003.txt
spam0004.txt
spam0005.txt
spam5.dat (unchanged wrong filetype)
spam0006.txt
spam0007.txt
spam0008.txt
spam0009.txt
spam0010.txt
Also an offset can be added to start the numbering for example at 5 or 0 instead of.
filling_gaps.py
"""
Filling_gaps.
Removes gaps in numbering of multiple files in a specified directory.
Takes a directory to search for files which match prefix and type and
otherwise only contain numbers in the middle.
Files are renamed continuesyly and filled up with leading zeros if
necessary
It is possible to define with which number the numeration
starts
e.g.
spam001.txt
spam003.txt
spam013.txt
spam1180.txt
would become (if start_number = 1):
spam0001.txt
spam0002.txt
spam0003.txt
spam0004.txt
To prevent overriting in the renaming process a temporary directory is
created during the renaming.
"""
import os
import sys
import shutil
import stat
from typing import List
def get_filenames(directory: str) -> List[str]:
"""
get filenames in supplied absolute directory name
"""
os.chdir(directory)
return os.listdir('.')
def get_digits_minimum_length(filenames: List[str], file_prefix: str,
file_type: str, start_number: int) -> int:
"""
loops over all provided filenames to find the filename with the
longest digit string e.g:
spam00001.txt -> len = 5
spam02.txt -> len = 2
spam0000003.txt -> len = 7
digits_length == 7
"""
digits_length: int = 0
for filename in filenames:
if filename.startswith(file_prefix) and filename.endswith(file_type):
number_string: str = filename.lstrip(file_prefix)
number_string = number_string.rstrip(file_type)
if number_string.isdigit():
current_length = len(number_string)
if current_length > digits_length:
digits_length = current_length
if len(str(start_number)) > digits_length:
digits_length = start_number
return digits_length
def make_filename(file_prefix: str, file_type: str, number: int,
number_length: int) -> str:
"""
Creates a new filename out of the parts.
"""
return file_prefix + str(number).zfill(number_length) + file_type
def make_folder(directory: str, folder_name: str):
"""
Creates a folder inside a directory
"""
new_directory: str = os.path.join(directory, folder_name)
os.makedirs(new_directory)
os.chmod(new_directory, stat.S_IWRITE)
def rename_and_move_file(directory: str, new_directory: str,
old_filename: str, new_filename: str):
"""
Renames a file from a specific directory into a new directory to
prevent that renamoving overrites other existing files
"""
shutil.move(os.path.join(directory, old_filename),
os.path.join(new_directory, new_filename))
def erase_directory(delete_directory: str):
"""
checks if directory exists and deletes it.
"""
if os.path.exists(delete_directory):
shutil.rmtree(delete_directory)
def extract_digits_and_move_to_temp(
directory: str, temp_directory: str, file_prefix: str, file_type: str,
digit_lenght: int):
"""
Remove pre und postfix from files, add leading zeros to the
integer part to reach the same length for all digitsand copy them
to temp directory
"""
for filename in get_filenames(directory):
if filename.startswith(file_prefix) and filename.endswith(file_type):
number_string: str = filename.lstrip(file_prefix)
number_string = number_string.rstrip(file_type)
if number_string.isdigit():
rename_and_move_file(directory, temp_directory, filename,
number_string.zfill(digit_lenght))
def renumber_and_move_to_directory(
start_number: int, directory: str, temp_directory: str,
file_prefix: str, file_type: str, digit_lenght: int):
"""
Renumbers the files according to start file number and moves them
back to directory
"""
next_number: int = start_number
filenames = get_filenames(temp_directory)
filenames.sort()
for filename in filenames:
new_filename: str = make_filename(
file_prefix, file_type, next_number, digit_lenght)
rename_and_move_file(temp_directory, directory, filename, new_filename)
next_number = next_number + 1
def filling_gaps(directory: str, file_prefix: str, file_type: str,
start_number: int = 1):
"""
Main logic of the filling gaps script.
First a temporary directory is made for the file operations.
Then the max len of digits (int + leading zeros) is calculated out
of the files.
Then all matched files are stripped everything but the int part and
they are moved into the temp directory.
In the temp directory they are numerated correctly again and moved
back to the source directory.
In the end the temporary directory is deleted
"""
if start_number < 0:
raise Exception("Start file number must be >=0. Value: " +
str(start_number))
temp_folder_name = "temp"
temp_directory = os.path.join(directory, temp_folder_name)
if os.path.exists(temp_directory):
print("Error: temp directory used for internal copy already exists."
" Make sure it does not. directory name:" + temp_folder_name)
sys.exit(0)
make_folder(directory, temp_folder_name)
digits_length: int = get_digits_minimum_length(
get_filenames(directory), file_prefix, file_type, start_number)
extract_digits_and_move_to_temp(
directory, temp_directory, file_prefix, file_type, digits_length)
renumber_and_move_to_directory(
start_number, directory, temp_directory, file_prefix, file_type,
digits_length)
erase_directory(temp_directory)
filling_gaps("E:\\Spam", "spam", ".txt", 1)
I would like to know if this is good code? Is the approach a good one? Do you understand the code from the comments and the docstrings? Are there any bad practices you can spot Are there any improvements / better approaches to try?
Also I tested this script by manually adding the example script again and again how could this be better tested?
Please let me know what you think.