Move a File/Directory in Python: shutil.move

Posted: | Tags: Python, File

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.

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().

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

Related Categories

Related Articles