note.nkmk.me

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

Date: 2018-09-26 / tags: Python, ファイル操作

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/'))
# []

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()などで正規表現を使った判定を行う。

リストを取得したい場合は以下のようにリスト内包表記が使える。

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()**を使うと時間がかかる可能性があるので、他の特殊文字で条件を絞れる場合はそちらを使ったほうがいい。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事