I'm a Python newbie. I have a directory with hundreds of files. I would like to add a progressive number at the beginning of each file, for example: 001_filename and so on ... I would like to use three digits for the numbering. Thanks to those who can help me!
The following Python script renames all the files in a given directory.
It first checks whether the path provided is actually a directory and if it exists. If not it prints an error message and leaves the program with a non-zero exit code.
Otherwise, it retrieves all the filenames in that directory and sorts the files according to their last modified date. You can easily adjust the sorting criteria by providing another lambda function for key=
argument of sorted()
. This is not actually required but as os.listdir()
gets the result in an order that is determined by your filesystem this is the only way to make sure there are renamed according to a criteria you specify.
After that it determines the number of digits required to uniquely identify each file with an unique ID. This will make sure the script works even if there are more than hundreds of files in the directory (like in your case).
To adhere to your question though using max()
I make sure it will always be at least three digits, even if they would not be required to uniquely identify a file.
It then finally renames the file if that is necessary i.e. the file does not already have the desired filename.
import os
import sys
dir = "mydir"
if not (os.path.exists(dir) and os.path.isdir(dir)):
# write error message to stderr
print(f"Directory {dir} does not not exist or is not a directory.", file=sys.stderr)
# exit program with exit code 1 indicating the script has failed
sys.exit(1)
# get all files in the directory and store for each file it's name and the full path to the file
# This way we won't have to create the full path many times
my_files = [(file, os.path.join(dir, file)) for file in os.listdir(dir) if os.path.isfile(os.path.join(dir, file))]
# sort by "modified" timestamp in reverse order => file with most recent modified date first
# we need to use the fullpath here which is the second element in the tuple
sorted_by_creation_date = sorted(my_files, key=lambda file: os.path.getmtime(file[1]), reverse=True)
# get number of digits required to assign a unique value
number_of_digits = len(str(len(my_files)))
# use at least 3 digits, even if that's not actually required to uniquely assign values
number_of_digits = max(3, number_of_digits)
# loop over all files and rename them
print("Renaming files...")
for index, (file, fullpath) in enumerate(my_files):
# rename files with leading zeros and start with index 1 instead of 0
new_filename = f"file{index + 1}.txt"#f"{index + 1:0{number_of_digits}d}_{file}" #f"file{index + 1}.txt"
if new_filename == file:
# move on to next file if the file already has the desired filename
print(f"File already has the desired filename {file}. Skipping file.")
continue
# concatenate new filename with path to directory
new_name = os.path.join(dir, new_filename)
# rename the file
print(f"{file} => {new_filename}")
os.rename(fullpath, new_name)
print("Done.")
If you want to run the script in a certain directory using python myscript.py
and rename the files in that directory you can use dir = os.getcwd()
to get the current working directory in your script instead of hardcoding the dir like in the script above (dir = "mydir"
).
This implementation does not rename files in nested directories, but you could adjust the program to do that if required. Probably want to have a look at os.walk() in that case.
Example for a directory like this:
Note: this uses the script from above with the hard-coded directory name
mydir
.
mydir/
├── file1.txt
├── file2.txt
├── file3.txt
└── Test
└── test.txt
Run the script
python myscript.py
Script output
Renaming files...
file1.txt => 001_file1.txt
file2.txt => 002_file2.txt
file3.txt => 003_file3.txt
Done.
Result
mydir/
├── 001_file1.txt
├── 002_file2.txt
├── 003_file3.txt
└── Test
└── test.txt
As you can see the files are renamed as expected and nested directories remain untouched.