Pythonでファイル・ディレクトリを移動するshutil.move

Modified: | Tags: Python, ファイル処理

Pythonでファイル・ディレクトリ(フォルダ)を移動するにはshutil.move()を使う。

移動ではなく削除やコピーをしたい場合は以下の記事を参照。

本記事のサンプルコードでは以下のようにshutilモジュールをインポートしている。標準ライブラリに含まれているので追加のインストールは不要。

import shutil

以下のファイル・ディレクトリ構造を例とする。この初期状態からファイルやディレクトリを移動させていく。

temp/
├── dir1/
│   ├── file.txt
│   └── subdir/
│       └── subfile.txt
├── dir2/
└── dir3/
    ├── file.txt
    └── subdir/

shutil.move()の基本的な使い方

shutil.move()の第一引数に移動させたいファイルやディレクトリのパス、第二引数に移動先のディレクトリのパスを指定する。移動後のファイルやディレクトリのパスが返される。

パスはパス文字列やpathlib.Pathなどのpath-like objectで指定する。パス文字列でのディレクトリ指定時の末尾の区切り文字(/)はあってもなくてもよい。

移動先に新規パス(存在しないパス)を指定した場合は移動してリネームされる。詳細は後述。

ファイルを移動

ファイルを別のディレクトリに移動する。

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/

移動先に同名のファイルが存在する場合はエラーになる。

# new_path = shutil.move('temp/dir2/file.txt', 'temp/dir3')
# Error: Destination path 'temp/dir3/file.txt' already exists

移動先として既存のファイルを指定した場合は上書きになる。後述。

ディレクトリを移動

ディレクトリを別のディレクトリに移動する。配下のファイルやディレクトリも丸ごと移動される。

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/

移動先に同名のディレクトリが存在する場合や、移動先として既存のファイルを指定した場合はエラーとなる。

# 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'

以降、dir3は必要ないので削除しておく。

shutil.rmtree('temp/dir3')

移動してリネーム

ファイルを移動してリネーム

ファイルの移動先に新規パス(存在しないパス)を指定すると移動してリネームされる。

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

移動先に存在しない中間ディレクトリがあるとエラーになる。あらかじめ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'

なお、新規のディレクトリを指定しようとして例えばtemp/new_dirと指定するとnew_dirというファイルとしてリネームされるので注意。temp/new_dir/と指定するとエラー。

ディレクトリを移動してリネーム

ディレクトリの移動先に新規パス(存在しないパス)を指定すると移動してリネームされる。

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/

ディレクトリの場合は存在しない中間ディレクトリも自動的に作成される。

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

移動先が存在するファイルの場合は上書き

ファイルの移動先に既存ファイルのパスを指定した場合、中身が移動元のファイルで上書きされる。ファイル名はそのままだが元の内容は失われるので注意。

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

中身が上書きされている。

with open('temp/dir1/file_new.txt') as f:
    print(f.read())
# text in file_other.txt

なお、移動先が既存のディレクトリの場合は、これまでの例のようにそのディレクトリに移動される。

ディレクトリ内のすべてのファイル・ディレクトリを別のディレクトリに移動

上述のように、shutil.move()でディレクトリを移動するとその中のファイル・ディレクトリもそのまま移動する。親ディレクトリはそのままで配下のファイル・ディレクトリを移動させたい場合は、一覧を取得して移動させる。

ファイルとサブディレクトリを含むディレクトリdir1と空のディレクトリdir2を例とする。

temp/
├── dir1/
│   ├── dir/
│   └── file.txt
└── dir2/

os.listdir()を使うと指定したディレクトリ内に存在するファイル・ディレクトリ名の一覧がリストで取得できるので、それらをすべて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

os.listdir()で得られるのはファイル名・ディレクトリ名のみなので、os.path.join()で元のディレクトリと連結してファイルパスを生成している。

複数のファイルを一括で移動

shutil.move()を使った実践的な例として、条件に応じて複数のファイルを一括で移動する方法を説明する。

なお、ファイル・ディレクトリ数が多い場合にglob.glob()**を使うと時間がかかる可能性があるので、他の特殊文字で条件を絞れるのであればそちらを使ったほうがいい。

ワイルドカードで条件指定

globモジュールを使うとワイルドカード*などの特殊文字を使ってファイルやディレクトリの一覧をリストで取得できる。詳しい使い方は以下の記事を参照。

元のディレクトリ構造を保持しない

以下のファイル・ディレクトリ構造を例とする。

temp/
└── dir1/
    ├── 123.jpg
    ├── 456.txt
    ├── abc.txt
    └── subdir/
        ├── 000.csv
        ├── 789.txt
        └── xyz.jpg

サブディレクトリ内も含めて、拡張子がtxtのファイルを抽出して移動する。移動先のディレクトリはあらかじめ生成しておく必要がある。

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

元のディレクトリ構造を保持する

移動元のディレクトリ構造を保持したい場合は、例えば以下のようにする。

以下のファイル・ディレクトリ構造を例とする(上と同じ)。

temp/
└── dir1/
    ├── 123.jpg
    ├── 456.txt
    ├── abc.txt
    └── subdir/
        ├── 000.csv
        ├── 789.txt
        └── xyz.jpg

glob.glob()の引数root_dirを指定して、os.path.dirname()によるディレクトリ名の抽出、os.path.join()によるパスの連結を利用する。処理中に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

正規表現で条件指定

正規表現で条件を指定したい場合はreモジュールを使う。glob.glob()だけでは実現できない複雑な条件を指定できる。

基本的な考え方は上述のglob.glob()のみで抽出する場合と同じ。

元のディレクトリ構造を保持しない

以下のファイル・ディレクトリ構成を例とする。

temp/
└── dir1/
    ├── 123.jpg
    ├── 456.txt
    ├── abc.txt
    └── subdir/
        ├── 000.csv
        ├── 789.txt
        └── xyz.jpg

glob.glob()**と引数recursive=Trueを使ってすべてのファイル・ディレクトリを再帰的にリストアップして、re.search()などで正規表現による判定を行う。

サブディレクトリ内も含めて、ファイル名が数字のみで拡張子がtxtまたはcsvのファイルを抽出して移動する。\dは数字、+は1回以上の繰り返し、(a|b)abのいずれかにマッチする。

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

元のディレクトリ構造を保持する

移動元のディレクトリ構造を保持したい場合は、例えば以下のようにする。

以下のファイル・ディレクトリ構成を例とする(上と同じ)。

temp/
└── dir1/
    ├── 123.jpg
    ├── 456.txt
    ├── abc.txt
    └── subdir/
        ├── 000.csv
        ├── 789.txt
        └── xyz.jpg

glob()のみで抽出する場合と同様に処理できる。

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

関連カテゴリー

関連記事