Move a File/Directory in Python: shutil.move
In Python, shutil.move()
allows you to move a file or a directory (folder).
If you want to delete or copy files and directories, refer to the following articles.
- Delete a file/directory in Python (os.remove, shutil.rmtree)
- Copy a file/directory in Python (shutil.copy, shutil.copytree)
The sample code in this article imports the shutil
module as shown below. It is part of the standard library, so no additional installation is necessary.
import shutil
The following initial state of files and directories will be used as examples for the various move operations in this article.
temp/
├── dir1/
│ ├── file.txt
│ └── subdir/
│ └── subfile.txt
├── dir2/
└── dir3/
├── file.txt
└── subdir/
Basic usage of shutil.move()
The first argument should be the path of the source file or directory, and the second should be the path of the destination directory. The function returns the path of the moved file or directory.
Paths can be represented as either strings or path-like objects like pathlib.Path
. When specifying a directory as a string, the trailing delimiter (/
) is optional.
If you specify a non-existent path as the destination, the object will be moved and renamed. More on this later.
Move a file
Move a file to another directory:
new_path = shutil.move('temp/dir1/file.txt', 'temp/dir2')
print(new_path)
# temp/dir2/file.txt
temp/
├── dir1/
│ └── subdir/
│ └── subfile.txt
├── dir2/
│ └── file.txt
└── dir3/
├── file.txt
└── subdir/
Be aware that an error is thrown if a file with the same name already exists in the destination.
# new_path = shutil.move('temp/dir2/file.txt', 'temp/dir3')
# Error: Destination path 'temp/dir3/file.txt' already exists
If an existing file is specified as the destination, it will be overwritten. More on this later.
Move a directory
Move a directory to another directory; all files and directories within it will also be moved recursively.
new_path = shutil.move('temp/dir1/subdir', 'temp/dir2')
print(new_path)
# temp/dir2/subdir
temp/
├── dir1/
├── dir2/
│ ├── file.txt
│ └── subdir/
│ └── subfile.txt
└── dir3/
├── file.txt
└── subdir/
An error occurs if a directory with the same name exists at the destination or an existing file is specified as the destination.
# new_path = shutil.move('temp/dir2/subdir', 'temp/dir3')
# Error: Destination path 'temp/dir3/subdir' already exists
# new_path = shutil.move('temp/dir2/subdir', 'temp/dir2/file.txt')
# FileExistsError: [Errno 17] File exists: 'temp/dir2/file.txt'
Remove dir3
as it is no longer needed for the explanation.
shutil.rmtree('temp/dir3')
Move and rename
Move and rename a file
By specifying a new (non-existent) path as the file's destination, you can move and rename the file.
new_path = shutil.move('temp/dir2/file.txt', 'temp/dir1/file_new.txt')
print(new_path)
# temp/dir1/file_new.txt
temp/
├── dir1/
│ └── file_new.txt
└── dir2/
└── subdir/
└── subfile.txt
If the destination includes a non-existent intermediate directory, an error will occur. In such cases, create the parent directories in advance using os.makedirs()
.
# new_path = shutil.move('temp/dir1/file_new.txt', 'temp/dir2/dir_new/file_new.txt')
# FileNotFoundError: [Errno 2] No such file or directory: 'temp/dir2/dir_new/file_new.txt'
Note that if you attempt to specify a new directory as the second argument (for example, temp/new_dir
), it will be interpreted as a new filename and the file will be renamed to new_dir
. Trying to specify temp/new_dir/
will cause an error. You must first create a directory.
Move and rename a directory
By specifying a new (non-existent) path as the directory's destination, you can move and rename the directory.
new_path = shutil.move('temp/dir2/subdir', 'temp/dir1/subdir_new')
print(new_path)
# temp/dir1/subdir_new
temp/
├── dir1/
│ ├── file_new.txt
│ └── subdir_new/
│ └── subfile.txt
└── dir2/
In the case of directories, any non-existent intermediate directories in the destination are created automatically.
new_path = shutil.move('temp/dir1/subdir_new', 'temp/dir2/subdir/subdir2')
print(new_path)
# temp/dir2/subdir/subdir2
temp/
├── dir1/
│ └── file_new.txt
└── dir2/
└── subdir/
└── subdir2/
└── subfile.txt
Overwrite an existing file at the destination
If the path of an existing file is specified as the destination for a file, the existing file will be overwritten with the content of the source file. The filename stays the same, but the original content is replaced, so exercise caution.
with open('temp/dir2/file_other.txt', 'w') as f:
f.write('text in file_other.txt')
new_path = shutil.move('temp/dir2/file_other.txt', 'temp/dir1/file_new.txt')
print(new_path)
# temp/dir1/file_new.txt
temp/
├── dir1/
│ └── file_new.txt
└── dir2/
└── subdir/
└── subdir2/
└── subfile.txt
This operation overwrites the contents.
with open('temp/dir1/file_new.txt') as f:
print(f.read())
# text in file_other.txt
Note that if the destination is an existing directory, the file will be moved into that directory, as in the previous examples.
Move all files and directories from one directory to another
As mentioned above, when you move a directory with shutil.move()
, all files and directories within it are moved. If you want to move the contained files and directories without moving the parent directory itself, you can generate a list and move each item.
For example, consider a directory dir1
with files and subdirectories, and an empty directory dir2
.
temp/
├── dir1/
│ ├── dir/
│ └── file.txt
└── dir2/
You can use os.listdir()
to get a list of the file and directory names in the specified directory, and then move them individually with shutil.move()
.
import shutil
import os
src_dir = 'temp/dir1'
dst_dir = 'temp/dir2'
for p in os.listdir(src_dir):
shutil.move(os.path.join(src_dir, p), dst_dir)
temp/
├── dir1/
└── dir2/
├── dir/
└── file.txt
Since os.listdir()
returns only the names of files and directories, you must concatenate them with the original directory path using os.path.join()
.
Move multiple files based on certain conditions with wildcards and regex
As a practical use case for shutil.move()
, let's see how to move multiple files based on certain conditions.
Please note that using **
in glob.glob()
can be time-consuming with a large number of files and directories. For improved efficiency, consider specifying conditions with other special characters when feasible.
Use wildcards to specify conditions
The glob
module allows you to use wildcard characters like *
to generate a list of file and directory names. For detailed usage, refer to the following article.
Without preserving the original directory structure
Consider the following files and directories:
temp/
└── dir1/
├── 123.jpg
├── 456.txt
├── abc.txt
└── subdir/
├── 000.csv
├── 789.txt
└── xyz.jpg
For example, you can move all txt
files including those in subdirectories. You need to create the destination directory in advance.
import shutil
import glob
import os
os.makedirs('temp/dir2')
for p in glob.glob('temp/dir1/**/*.txt', recursive=True):
shutil.move(p, 'temp/dir2')
temp/
├── dir1/
│ ├── 123.jpg
│ └── subdir/
│ ├── 000.csv
│ └── xyz.jpg
└── dir2/
├── 456.txt
├── 789.txt
└── abc.txt
With preserving the original directory structure
If you want to maintain the structure of the source directory, you could do it as follows.
Consider the following files and directories:
temp/
└── dir1/
├── 123.jpg
├── 456.txt
├── abc.txt
└── subdir/
├── 000.csv
├── 789.txt
└── xyz.jpg
Specify the root_dir
argument in glob.glob()
, use os.path.dirname()
to extract the directory name, and use os.path.join()
to concatenate paths. You don't need to create the destination directory in advance because it's generated with os.makedirs()
.
- Get the filename, directory, extension from a path string in Python
- Create a directory with mkdir(), makedirs() in Python
src_dir = 'temp/dir1'
dst_dir = 'temp/dir2'
for p in glob.glob('**/*.txt', recursive=True, root_dir=src_dir):
os.makedirs(os.path.join(dst_dir, os.path.dirname(p)), exist_ok=True)
shutil.move(os.path.join(src_dir, p), os.path.join(dst_dir, p))
temp/
├── dir1/
│ ├── 123.jpg
│ └── subdir/
│ ├── 000.csv
│ └── xyz.jpg
└── dir2/
├── 456.txt
├── abc.txt
└── subdir/
└── 789.txt
Use regex to specify conditions
For complex conditions that can't be handled by glob alone, use the re
module to specify conditions with regular expressions.
The basic approach remains the same as when using glob.glob()
alone.
Without preserving the original directory structure
Consider the following files and directories:
temp/
└── dir1/
├── 123.jpg
├── 456.txt
├── abc.txt
└── subdir/
├── 000.csv
├── 789.txt
└── xyz.jpg
Use glob.glob()
with **
and recursive=True
to recursively list all files and directories, and then filter them by regex with re.search()
.
For example, to move files with names consisting only of digits and either a txt
or csv
extension, including files in subdirectories, use \d
to match digits, +
to match one or more repetitions, and (a|b)
to match either a
or b
.
import shutil
import glob
import os
import re
os.makedirs('temp/dir2')
for p in glob.glob('temp/**', recursive=True):
if re.search('\d+\.(txt|csv)', p):
shutil.move(p, 'temp/dir2')
temp/
├── dir1/
│ ├── 123.jpg
│ ├── abc.txt
│ └── subdir/
│ └── xyz.jpg
└── dir2/
├── 000.csv
├── 456.txt
└── 789.txt
With preserving the original directory structure
If you want to maintain the source directory's structure, you could do it as follows:
Consider the following files and directories:
temp/
└── dir1/
├── 123.jpg
├── 456.txt
├── abc.txt
└── subdir/
├── 000.csv
├── 789.txt
└── xyz.jpg
The operation is similar to using glob.glob()
alone.
src_dir = 'temp/dir1'
dst_dir = 'temp/dir2'
for p in glob.glob('**', recursive=True, root_dir=src_dir):
if re.search('\d+\.(txt|csv)', p):
os.makedirs(os.path.join(dst_dir, os.path.dirname(p)), exist_ok=True)
shutil.move(os.path.join(src_dir, p), os.path.join(dst_dir, p))
temp/
├── dir1/
│ ├── 123.jpg
│ ├── abc.txt
│ └── subdir/
│ └── xyz.jpg
└── dir2/
├── 456.txt
└── subdir/
├── 000.csv
└── 789.txt