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

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

shutil.move() --- 高水準のファイル操作 — Python 3.7.0 ドキュメント

shutilモジュールは標準ライブラリに含まれているので追加のインストールは不要(importは必要)。 以下の内容について説明します。

shutil.move()の基本的な使い方 移動してリネーム 移動先が存在するファイルの場合 ディレクトリ内のすべてのファイル・ディレクトリを別のディレクトリに移動 複数のファイルを一括で移動 ワイルドカードで条件指定 正規表現で条件指定移動ではなく削除したい場合は

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

例として、以下のようにファイルとサブディレクトリを含むディレクトリdir1と空のディレクトリdir2を作成します。

import shutil
import os

os.makedirs('temp/dir1/dir', exist_ok=True)
os.makedirs('temp/dir2', exist_ok=True)

with open('temp/dir1/file.txt', 'w') as f:
    f.write('original')

print(os.listdir('temp/dir1/'))
# ['file.txt', 'dir']

print(os.listdir('temp/dir2/'))
# []

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

new_path = shutil.move('temp/dir1/file.txt', 'temp/dir2/')

print(new_path)
# temp/dir2/file.txt

print(os.listdir('temp/dir1/'))
# ['dir']

print(os.listdir('temp/dir2/'))
# ['file.txt']

存在しないディレクトリにファイルを移動しようとするとエラーになります。

# new_path = shutil.move('temp/dir2/file.txt', 'temp/dir1/new_dir/')
# FileNotFoundError: [Errno 2] No such file or directory: 'temp/dir1/new_dir/'

os.mkdir()やos.makedirs()などでディレクトリを作成しておく必要があります。

ディレクトリを移動 ディレクトリを別のディレクトリに移動します。ファイルの場合と同じ。

new_path = shutil.move('temp/dir1/dir/', 'temp/dir2/')

print(new_path)
# temp/dir2/dir

print(os.listdir('temp/dir1/'))
# []

print(os.listdir('temp/dir2/'))
# ['file.txt', 'dir']

ディレクトリの場合、新規ディレクトリを移動先に指定するとリネームされます。後述します。

移動してリネーム

ファイルを移動してリネーム ファイルの移動時に新規のファイルのパスを移動先に指定すると移動してリネームされます。

new_path = shutil.move('temp/dir2/file.txt', 'temp/dir1/file_new.txt')

print(new_path)
# temp/dir1/file_new.txt

print(os.listdir('temp/dir1/'))
# ['file_new.txt']

print(os.listdir('temp/dir2/'))
# ['dir']

直上のディレクトリまでは生成しておく必要があります。移動先に存在しない中間ディレクトリがあるとエラーとなります。

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

ディレクトリを移動してリネーム ディレクトリの移動時に新規のディレクトリのパスを移動先に指定すると移動してリネームされます。

new_path = shutil.move('temp/dir2/dir/', 'temp/dir1/dir_new/')

print(new_path)
# temp/dir1/dir_new/

print(os.listdir('temp/dir1/'))
# ['dir_new', 'file_new.txt']

print(os.listdir('temp/dir2/'))
# []

ディレクトリの移動の場合は存在しない中間ディレクトリも生成してくれる。

new_path = shutil.move('temp/dir1/dir_new', 'temp/dir2/dir_new/dir_new2/')

print(new_path)
# temp/dir2/dir_new/dir_new2/

print(os.listdir('temp/dir1/'))
# ['file_new.txt']

print(os.listdir('temp/dir2/'))
# ['dir_new']

print(os.listdir('temp/dir2/dir_new/'))
# ['dir_new2']

移動先が存在するファイルの場合

ファイルの移動時にすでに存在しているファイルのパスを移動先に指定した場合の動作はos.rename()の動作に依存します。

os.rename() --- 雑多なオペレーティングシステムインタフェース — Python 3.7.0 ドキュメント

Macを含むUNIX系のOSでは上書きされ、Windowsではエラーとなります。 以下はMacの例を示します。移動先のファイルの内容が上書きされます。

with open('temp/dir2/file_other.txt', 'w') as f:
    f.write('other')

new_path = shutil.move('temp/dir1/file_new.txt', 'temp/dir2/file_other.txt')

print(new_path)
# temp/dir2/file_other.txt

print(os.listdir('temp/dir1/'))
# []

print(os.listdir('temp/dir2/'))
# ['file_other.txt', 'dir_new']

with open('temp/dir2/file_other.txt') as f:
    print(f.read())
# original

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

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

これまでの例と同じく、以下のようにファイルとサブディレクトリを含むディレクトリdir1と空のディレクトリdir2を作成します。

import shutil
import os

os.makedirs('temp/dir1/dir', exist_ok=True)
os.makedirs('temp/dir2', exist_ok=True)

with open('temp/dir1/file.txt', 'w') as f:
    f.write('original')

print(os.listdir('temp/dir1/'))
# ['file.txt', 'dir']

print(os.listdir('temp/dir2/'))
# []

os.listdir()を使うと指定したディレクトリ内に存在するファイル・ディレクトリ名の一覧がリストで取得できるので、それらをすべてshutil.move()で移動すれば問題ありません。

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)

print(os.listdir(src_dir))
# []

print(os.listdir(dst_dir))
# ['file.txt', 'dir']

元のディレクトリとos.listdir()で得られるファイル名・ディレクトリ名をos.path.join()で連結して移動元のパスとしています。

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

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

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

globモジュールを使うとワイルドカード*などの特殊文字(メタ文字)を使ってファイルやディレクトリの一覧をリストで取得できます。

以下のようなファイル・ディレクトリ構成を例とします。 temp/ ├── 012.txt ├── abc.txt ├── dir │ ├── 789.txt │ └── xyz.text └── file.text

glob.glob()の第一引数にワイルドカードなどを含む文字列を指定します。

import glob

print(glob.glob('temp/*.txt'))
# ['temp/abc.txt', 'temp/012.txt']

Python3.5からは**と引数recursive=Trueを合わせて再帰的な処理ができます。

print(glob.glob('temp/**', recursive=True))
# ['temp/', 'temp/file.text', 'temp/abc.txt', 'temp/012.txt', 'temp/dir', 'temp/dir/xyz.text', 'temp/dir/789.txt']

サブディレクトリ内も含めて任意の拡張子のファイルのリストを取得するには以下のようにします。

print(glob.glob('temp/**/*.txt', recursive=True))
# ['temp/abc.txt', 'temp/012.txt', 'temp/dir/789.txt']

print(glob.glob('temp/**/*.text', recursive=True))
# ['temp/file.text', 'temp/dir/xyz.text']

?は任意の一文字、[]はカッコ内の一文字にマッチします。

print(glob.glob('temp/**/???.text', recursive=True))
# ['temp/dir/xyz.text']

print(glob.glob('temp/**/[0-9][0-9][0-9].txt', recursive=True))
# ['temp/012.txt', 'temp/dir/789.txt']

正規表現ではないので+で1回以上の繰り返しを指定したりはできません。正規表現を使った例は後述します。 以下のような関数を定義するとglob.glob()で抽出された複数のファイルを一括で移動できます。

import shutil
import glob
import os

def move_glob(dst_path, pathname, recursive=True):
    for p in glob.glob(pathname, recursive=recursive):
        shutil.move(p, dst_path)

サブディレクトリ内も含めて拡張子txtのファイルを移動します。

os.mkdir('temp/dir2')

move_glob('temp/dir2', 'temp/**/*.txt')

移動後のファイル・ディレクトリ構成は以下の通り。 temp/ ├── dir │ └── xyz.text ├── dir2 │ ├── 012.txt │ ├── 789.txt │ └── abc.txt └── file.text

正規表現で条件指定

正規表現で条件を指定したい場合はreモジュールを使います。

上の例と同じく以下のようなファイル・ディレクトリ構成を例とします。 temp/ ├── 012.txt ├── abc.txt ├── dir │ ├── 789.txt │ └── xyz.text └── file.text

glob.glob()で**と引数recursive=Trueを使ってすべてのファイル、ディレクトリを再帰的にリストアップしてからre.search()などで正規表現を使った判定を行う。 リストを取得したい場合は以下のようにリスト内包表記が使えます。

import glob
import re

print(glob.glob('temp/**', recursive=True))
# ['temp/', 'temp/file.text', 'temp/abc.txt', 'temp/012.txt', 'temp/dir', 'temp/dir/xyz.text', 'temp/dir/789.txt']

print([p for p in glob.glob('temp/**', recursive=True) if re.search('\d+\.txt', p)])
# ['temp/012.txt', 'temp/dir/789.txt']

print([p for p in glob.glob('temp/**', recursive=True) if re.search('/\D{3}\.(txt|text)', p)])
# ['temp/abc.txt', 'temp/dir/xyz.text']

\dは数字、+は1回以上の繰り返し。\Dは数字以外の文字、{n}はn回の繰り返し。また、(a|b)はa, bのいずれかとなります。globだけでは実現できない複雑な条件が指定できます。 以下のような関数を定義するとglob.glob()および正規表現で抽出された複数のファイルを一括で移動できます。

import shutil
import glob
import re
import os

def move_glob_re(dst_path, pattern, pathname, recursive=True):
    for p in glob.glob(pathname, recursive=recursive):
        if re.search(pattern, p):
            shutil.move(p, dst_path)

サブディレクトリ内も含めて拡張子txtでファイル名が数字のみのファイルを移動します。

os.mkdir('temp/dir2')

move_glob_re('temp/dir2', '\d+.txt', 'temp/**')

移動後のファイルは以下の通り。 temp/ ├── abc.txt ├── dir │ └── xyz.text ├── dir2 │ ├── 012.txt │ └── 789.txt └── file.text

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

Last Updated: 6/26/2019, 10:34:03 PM