Following on from this page about the os
and pathlib
modules, here’s how to move and rename files in Python:
The code on this page uses the os
, pathlib
and datetime
modules which come pre-installed with Python. Import them into your script as follows:
import os
from pathlib import Path
from datetime import datetime
Folders can be created with os.makedirs()
. Use the exist_ok=True
option to prevent an error being created if the folder already exists:
# Create Folders
os.makedirs('Input Folder', exist_ok=True)
os.makedirs('Output Folder', exist_ok=True)
Files can be created with open()
, write()
and close()
:
paths = [
'Input Folder/Example File 1.txt',
'Input Folder/Example File 2.txt',
'Input Folder/Example File 3.txt',
'Input Folder/Example File 4.txt',
]
# Create files
for path in paths:
# Open file in 'write' mode
f = open(path, 'w+')
# Write to a file
f.write('Hi, mom')
# Close the file
f.close()
Define the input- and output-folders where you want to move your files from and to:
# Folder admin
move_from = 'Input Folder'
move_to = 'Output Folder'
Although strings can be used for this (as has been done above), it’s good practice to instead use path objects from the pathliib
module:
# Folder admin
move_from = Path('Input Folder')
move_to = Path('Output Folder')
It’s more than just the fact that Path objects can be easier to work with, it’s the principle:
In general, you want objects to be what they are: numbers should be ints or floats, names should be strings and file paths should be path objects. This helps to ensure that your code does what you want it to do and makes it easier for anyone reading your code to work out what it does.
We can list the files in our input directory with the os.listdir()
function:
# List the files in a directory
filenames = os.listdir(move_from)
print(filenames)
## ['Example File 1.txt', 'Example File 2.txt', 'Example File 3.txt', 'Example File 4.txt']
These files won’t necessarily be in alphabetical order. If you want them to be, it’s worthwhile explicitly sorting them by using the sorted()
function on the list:
# List the files in a directory in alphabetical order
filenames = sorted(os.listdir(move_from))
print(filenames)
## ['Example File 1.txt', 'Example File 2.txt', 'Example File 3.txt', 'Example File 4.txt']
If there are some files you want to exclude from whatever it is you are doing, you can remove them from the list using the .remove()
method (not to be confused with the os.remove()
function which deletes files):
# Remove files
filenames.remove('Example File 3.txt')
print(filenames)
## ['Example File 1.txt', 'Example File 2.txt', 'Example File 4.txt']
If you are running your script in the same folder as the files you want to move/rename, you will see the name of your script included in os.listdir()
’s output. This isn’t a good thing - we don’t want a script to move and/or rename itself while you’re in the process of running it! You can exclude it manually using filenames.remove('Name of Script.py')
but, of course, you will need to change it if you ever copy this code into another file. A more robust way to do this would be to get the name of the script automatically using the __file__
parameter. This parameter (called “dunder file” - short for “double-underscore file”) is created automatically when you run a script and contains its path:
# Get the path of the current script
print(__file__)
## /home/username/Documents/Python Script.py
The file name can then be extracted using os.path.basename()
:
# Get the file name of the current script
print(os.path.basename(__file__))
## Python Script.py
This can be used in conjunction with .remove()
. However, if you then use your script with the input folder not being the current directory it will throw an error: the .remove()
function will try to remove your script’s name from the list created by os.listdir()
but it won’t be found. To get around this and make your script completely robust, first check that your script’s name is in the list of filenames you are working with:
# Remove the script's name from the list of files if it is in there
if (filename := os.path.basename(__file__)) in filenames:
filenames.remove(filename)
Python can’t be used to move files directly, but it can be used to run system commands in the terminal/command line. One such command is mv
which moves files, so that can be invoked for our purposes:
# Folder admin
move_from = Path('Input Folder')
move_to = Path('Output Folder')
# List the files in a directory
filenames = os.listdir(move_from)
# Remove the script's name from the list of files, if it is in there
if (filename := os.path.basename(__file__)) in filenames:
filenames.remove(filename)
# Move files from the input folder to the output folder
for filename in filenames:
path_in = Path(move_from, filename)
path_out = Path(move_to, filename)
os.system(f'mv "{path_in}" "{path_out}"')
Let’s check that it’s worked:
# Check what files are in the input folder
print(os.listdir(move_from))
## []
# Check what files are in the output folder
print(os.listdir(move_to))
## ['Example File 1.txt', 'Example File 2.txt', 'Example File 3.txt', 'Example File 4.txt']
This is done with the os.rename()
function. Of course, you can change a file’s name to whatever you want (provided you don’t use any of your filesystem’s reserved characters) but in this example I will change the files’ names by appending the timestamps from when they were last modified. This timestamp is provided by the os.path.getmtime()
function (get the modification time) and is a number that represents the time in ‘seconds since the Epoch’. We can convert that to a human-readable timestamp using the datetime
module:
# List the files in a directory
filenames = os.listdir(move_to)
# Iterate over all the files I want to rename
for filename in filenames:
# Get the paths to the files
old_path = Path(move_to, filename)
# Get the modification timestamps
last_modified = os.path.getmtime(old_path)
# Convert to datetime format
last_modified = datetime.fromtimestamp(int(last_modified))
# Create new filename
stem = old_path.stem
last_modified = str(last_modified).replace(':', '.')
extension = old_path.suffix
filename_new = stem + f' ({last_modified})' + extension
print(filename_new)
# Create new full path
new_path = Path(move_to, filename_new)
# Rename it
os.rename(old_path, new_path)
## Example File 1 (2023-12-24 19.38.17).txt
## Example File 2 (2023-12-24 19.38.17).txt
## Example File 3 (2023-12-24 19.38.17).txt
## Example File 4 (2023-12-24 19.38.17).txt
If you aren’t familiar with datetime objects it’s useful to point out that you can specify any date and time format using the .strftime()
(string from time) method:
# Get the time of last modification for this file
last_modified = os.path.getmtime(__file__)
# Convert to ISO format from seconds-since-epoch
last_modified = datetime.fromtimestamp(last_modified)
# Append the modification datetime to a filename
filename = 'Script.py'
new_name = str(last_modified).replace(':', '.') + ' ' + Path(filename).name
print(new_name)
## 2023-12-24 19.38.15.587625 Script.py
new_name = last_modified.strftime('%Y-%m-%d %H.%M.%S ') + Path(filename).name
print(new_name)
## 2023-12-24 19.38.15 Script.py
new_name = last_modified.strftime('%Y-%m-%d ') + Path(filename).name
print(new_name)
## 2023-12-24 Script.py
ctime vs mtime vs atime
The ‘ctime’ for a file is, on Unix machines (eg Linux and macOS), the time of the last metadata change while on Windows it is the time of the file’s creation. The ‘mtime’ is the time of the last modification of a file. The ‘atime’ is the last time it was accessed:
# Get the ctime for this file
ctime = os.path.getctime(__file__)
# Get the mtime for this file
mtime = os.path.getmtime(__file__)
# Get the atime for this file
atime = os.path.getatime(__file__)
print(ctime, 'vs', mtime, 'vs', atime, sep='\n')
## 1703446695.5876248
## vs
## 1703446695.5876248
## vs
## 1703446695.7516198
I have used the mtime in these examples.
The .replace()
method will find a sub-string within a string and replace it with a different sub-string. If it does not find the given sub-string it’s not a problem - the code won’t fail. We can use this to our advantage by iterating over a list of filenames and making replacements that affect some but not others:
# Check what the filenames are to begin with
for filename in sorted(os.listdir('Output Folder')):
print(filename)
## Example File 1 (2023-12-24 19.38.17).txt
## Example File 2 (2023-12-24 19.38.17).txt
## Example File 3 (2023-12-24 19.38.17).txt
## Example File 4 (2023-12-24 19.38.17).txt
# Update the file names
for filename in os.listdir('Output Folder'):
new_filename = filename.replace('Example File 1', '1st File')
new_filename = new_filename.replace('Example File 2', '2nd File')
new_filename = new_filename.replace('Example File 3', '3rd File')
new_filename = new_filename.replace('Example File 4', '4th File')
# Rename
old_path = Path('Output Folder', filename)
new_path = Path('Output Folder', new_filename)
os.rename(old_path, new_path)
Let’s confirm that this has worked:
# Check what the filenames are now
for filename in sorted(os.listdir('Output Folder')):
print(filename)
## 1st File (2023-12-24 19.38.17).txt
## 2nd File (2023-12-24 19.38.17).txt
## 3rd File (2023-12-24 19.38.17).txt
## 4th File (2023-12-24 19.38.17).txt
The ISO 8601 format specifies that dates should be represented as YYYY-MM-DD
and times as hh:mm:ss
, and this convention has been followed in the filenames above (except for the fact that the colons have been replaced with full stops because colons are reserved characters). Another ISO 8601 format is YYYYMMDDThhmmssZ
, which is more filename-friendly but a little less readable. Here’s how to convert from this second format to the first:
# The filename to change
filename = Path('20220906T215004Z.txt')
# Get the file root
file_root = filename.stem
# Get the extension
extension = filename.suffix
print(extension)
## .txt
# Convert the timestamp format
dt = datetime.strptime(file_root, '%Y%m%dT%H%M%SZ')
# Make it file name-friendly
file_root_new = str(dt).replace(':', '.')
print(file_root_new)
## 2022-09-06 21.50.04
# Construct the new filename
filename_new = file_root_new + extension
print(filename_new)
## 2022-09-06 21.50.04.txt
You can now use os.rename(filename, filename_new)
to rename the file.
The Exif (Exchangeable image file format) data from photos can be read using the Image
sub-module from PIL
(the Python Imaging Library). This is often more useful than the modification date when working with photos. Here’s how to access it:
from PIL import Image
# Provide the name of a photo
file = 'DSC02825.jpg'
# Get the date taken
date_taken = Image.open(file)._getexif()[306]
# or
date_taken = Image.open(file)._getexif()[36867]
print(date_taken)
## 2017:04:08 15:02:45
# Get the camera model
model = Image.open(file)._getexif()[272]
print(model)
## DSC-W130
Files can be deleted with os.remove()
:
for file in os.listdir('Output Folder'):
os.remove(Path('Output Folder', file))
This is not the same as the .remove()
method! That is used to remove an item from a list:
ls = ['A', 'B', 'C']
ls.remove('A')
print(ls)
## ['B', 'C']
Empty folders can be deleted with os.rmdir()
:
os.rmdir('Input Folder')
os.rmdir('Output Folder')