Pythonでファイル・ディレクトリを移動するshutil.move
Pythonでファイル・ディレクトリ(フォルダ)を移動するにはshutil.move()
を使う。
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/'))
# []
以下のような構造になる。
temp
├── dir1
│ ├── dir
│ └── file.txt
└── 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()
の動作に依存する。
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()
で移動すればOK。
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()
などで正規表現を使った判定を行う。
リストを取得したい場合は以下のようにリスト内包表記が使える。
- 関連記事: Pythonリスト内包表記の使い方
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()
で**
を使うと時間がかかる可能性があるので、他の特殊文字で条件を絞れる場合はそちらを使ったほうがいい。