Pythonでファイルやフォルダ操作を行う代表的なライブラリとして os、shutil 、glob がありますが、それらの違いや特徴を把握せず、何となく使っている方も多いのではないでしょうか?
今回は、これらライブラリの違いと特徴、及び具体的な事例をもとにした使い方のサンプルコードを紹介したいと思います。
尚、本記事はフォルダ名やファイル名を使った操作に限定しているため、ファイルに対する読み書き方法については「【図で解説】Python でファイル読み書きの基礎と実践。すぐに使えるサンプル満載」で詳しく解説しています。
os、shutil 、globの違いと特徴
os、shutil、glob はともにファイルやフォルダを操作するためのライブラリですが、それぞれ大きな違いがあります。
ライブラリ | 特徴 |
---|---|
os | WindowsやLinuxなどのオペレーティングシステムに対して基本的な操作を行うライブラリ であり、その一環としてファイルシステムに対する操作が可能です。 具体的には、指定したファイル又はフォルダに対して、作成、削除、名前変更、ファイル削除 などの操作が行なえます。 |
shutil | osのファイルシステムに関する機能を、フォルダ配下のファイルやフォルダを含めて 適用できるようにしたライブラリです。 指定したフォルダの階層を再帰的に検索し、作成、削除、移動、削除などの操作が可能です。 |
glob | 指定したパターンに一致する全てのフォルダやファイルのパスを取得します。 glob自身にファイルやフォルダの操作機能はありません。 |
以上の内容を表にまとめると次のようになります。
機能 | os | shutil | glob |
---|---|---|---|
コピー | × | ファイル、フォルダ | × |
移動 | × | ファイル、フォルダ | × |
削除 | ファイルのみ | フォルダのみ | × |
リネーム | ファイルのみ | ファイル、フォルダ | × |
フォルダ作成 | 〇 | × | × |
ファイル一覧取得 | × | × | 可能 |
このように、それぞれのライブラリごとに役割や機能が異なるため使い分ける必要があります。
利用のための準備
os,shutio,glob いずれもPython標準のライブラリであるため pip により別途インストールの必要はありませんが、利用の際は import をしておく必要があります。
import os
import shutil
import glob
用途別のメソッド紹介
ここからは、やりたいこと別にライブラリとメソッドを一覧にまとめています。
ファイルやフォルダの存在確認
os.path.exists() は、フォルダ、ファイルのどちらでも存在確認が可能です。どちらが存在したかを知りたい場合は、os.path.isdir()、又はos.path.isfile() を使います。
機能 | 記述例 | 説明 |
---|---|---|
ファイル又はフォルダの存在確認 | os.path.exists('path') | 指定したパス(フォルダ又はファイル)が 存在すれば True、無ければFalseが返る。 |
フォルダであるか否かの確認 | os.path.isdir('path') | 指定したパスがフォルダならTrue フォルダでなければFalseが返る。 |
ファイルであるか否かの確認 | os.path.isfile('path') | 指定したパスがファイルならTrue ファイルでなければFalseが返る。 |
import os
# 存在チェック
if os.path.exists("path/to/your/file.txt"):
print("ファイルまたはフォルダが存在します")
else:
print("ファイルまたはフォルダは存在しません")
# フォルダか否かの確認
if os.path.isdir("path/to/your/folder"):
print("指定されたパスはフォルダです")
else:
print("指定されたパスはフォルダではありません")
# ファイルか否かの確認
if os.path.isfile("path/to/your/file.txt"):
print("指定されたパスはファイルです")
else:
print("指定されたパスはファイルではありません")
フォルダの新規作成
os.mkdirs() に単一のフォルダを指定すると os.mkdir() の代用が出来ます。実用的にはフォルダが無ければ作成、あれば何もしないというケースが多いと思いますので、その際は os.path.exists() と組み合わせて使いましょう。
機能 | 記述例 | 説明 |
---|---|---|
フォルダの新規作成 | os.mkdir('myfolder') | フォルダを階層で指定する場合、その末端より上の フォルダが存在しないとエラーとなる。 また、指定したフォルダ(階層を指定した場合、 その末端)が既に存在すればエラーとなる。 |
フォルダの新規作成 (階層下のフォルダも作成) | os.mkdirs('myfolder') | フォルダを階層で指定すると、その階層を含めて 作成してくれる。 但し、指定したフォルダ(階層を指定した場合、 その末端)が既に存在すればエラーとなる。 |
import os
# 新しいディレクトリを作成 (親ディレクトリが存在している場合)
os.mkdir("new_folder")
# 複数の階層のディレクトリを作成
os.makedirs("path/to/new/folder")
フォルダ又はファイルのリネーム
ファイルおよびフォルダどちらにおいてもリネームすることが可能です。
機能 | 記述例 | 説明 |
---|---|---|
フォルダ又はファイル のリネーム | os.rename('oldname','newname') | oldname で指定したフォルダ又はファイルの 名前を、newnameで指定した名前に変更します。 変更後の名前が既に存在する場合エラーとなる。 |
import os
# ファイル名を変更
os.rename("old_file.txt", "new_file.txt")
# フォルダ名を変更
os.rename("old_folder", "new_folder")
ファイル又はフォルダのコピー
shutil.copy() は単一のファイルコピーをするメソッドです。第2引数にフォルダを指定する場合、そのフォルダ配下にコピーされます。一方 shutil.copytree() は指定したフォルダを丸ごと別のフォルダにコピーしてくれます。
shutil.copy()はコピー先にファイルが存在していても上書きしてくれますが、shutil.copytree()の場合はエラーとなるためご注意ください。
機能 | 記述例 | 説明 |
---|---|---|
ファイルコピー | shutil.copy('path1','path2') | path1は必ずファイルであること。 path2はファイル又はフォルダのどちらでも良い。 path2にフォルダを指定する場合、事前に作成 しておく必用がある。 |
フォルダコピー | shutil.copytree('path1','path2') | path1で指定したフォルダを階層ごと、path2で 指定したフォルダ配下にコピーする。 コピー先存が既に存在している場合はエラー となる。 |
import shutil
# ファイルのコピー
shutil.copy("data/original.txt", "backup/")
# フォルダのコピー (既存のフォルダを上書きしない)
shutil.copytree("project", "backup/project", dirs_exist_ok=True)
ファイル又はフォルダの移動
shutil.move() はファイルとフォルダの両方に対して移動することが可能です。
注意点として既に移動先が存在する場合、ファイル移動では移動先のファイルが置き換わります(元ファイルを削除後に移動される)が、フォルダ移動で既にフォルダが存在する場合、その直下に移動します。
shutil.move() をリネームの代用として使うことは可能ですが、移動先が存在する場合は警告もなく処理されてしまうため少々危険かもしれません。
機能 | 記述例 | 説明 |
---|---|---|
ファイルの移動 フォルダの移動 | shutil.move('path1','path2') | path1で指定したファイル又はフォルダを path2で指定した場所に移動する。 path1がフォルダの場合、path2の配下に丸ごと移動する。 path1とpath2に同じ階層を指定することで、フォルダやファイルのリネームとして代用することが可能。 |
import shutil
# ファイルを移動
shutil.move("data/old_data.csv", "results/")
# フォルダを移動
shutil.move("project", "backup")
# ファイル名を変更 (リネーム)
shutil.move("data.txt", "results.txt")
ファイル又はフォルダの削除
ファイル削除は os.remove()、フォルダ削除は shutil.rmtree() で使い分けが必要です。また、どちらも指定したファイル又はフォルダが存在しない場合はエラーになります。
こちらも有れば削除、なければ何もしないというケースが多いと思いますので、その場合は os.path.exists() との組み合わせが必要です。
機能 | 記述例 | 説明 |
---|---|---|
ファイルの削除 | os.remove('path') | pathで指定したファイルを削除する。 pathは必ずファイルを指定すること。 ファイルが存在しなければエラーとなる。 |
フォルダの削除 | shutil.rmtree('path') | path で指定したフォルダを階層ごと削除する。 path は必ずフォルダを指定すること。 フォルダが存在しないとエラーとなる。 |
import os
import shutil
# ファイルを削除
os.remove("temp.txt")
# フォルダを削除
shutil.rmtree("temp_folder")
ファイルまたはフォルダ一覧の取得
os.listdir() は指定したフォルダの直下にあるファイルやフォルダを取得したい場合にのみ使えます。
一方、glob.glob()の場合、recursive引数を省略するとフォルダ直下のファイルやフォルダだけが対象となりますが、recursive=Trueを指定することで、その配下にある全てのフォルダやファイルをリストアップしてくれます。
機能 | 記述例 | 説明 |
---|---|---|
ファイル一覧の取得 (直下のみ) | os.listdir('path') | 指定したフォルダ直下のファイル及びフォルダをリストで返す。 フォルダの階層を再帰的に検索してくれない。ワイルドカードも不可。 |
ファイル一覧の取得 (階層下全て) | os.walk('path') | 指定したフォルダの階層を再帰的に検索し、結果を_walk オブジェクトで返す。 for 文等のループを使って結果を取り出す。 |
ファイル一覧の取得 (直下、及び階層下全て) | glob.glob('path',recursive=True) | pathで指定したフォルダの階層を再帰的に検索し、結果をリストで返す。 pathが存在しない場合は空のリスト[] が返される。 pathにはワイルドカード(*や?) が指定可能。 例:glog.glob('e:/document/*.csv') |
import os
import glob
# listdirによるファイル一覧の取得
files = os.listdir("my_directory")
print(files)
# walkによるファイル一覧の取得
for root, dirs, files in os.walk("my_directory"):
for file in files:
print(os.path.join(root, file))
# globによるファイル一覧の取得
csv_files = glob.glob("data/*.csv", recursive=True)
print(csv_files)
glob.glob() のワイルドカードの指定が少し複雑なので、整理しておきます。
フォルダ配下のファイル又はフォルダを全て取得するには、ワイルドカードに ** を指定する必要があることにご注意ください。
>>> glob.glob("p:/data/")
['p:/data/']
>>>
# フォルダに*を1つだけ指定すると、直下のフォルダをファイルが表示
>>> glob.glob("p:/data/*")
['p:/data\\folder1', 'p:/data\\folder2', 'p:/data\\root.txt']
>>>
# フォルダにワイルドカードを指定すると、直下のファイルのみ表示
>>> glob.glob("p:/data/*.*")
['p:/data\\root.txt']
>>>
# フォルダに*を2つ指定し、recursive=True すると階層下を含めて表示
>>> glob.glob("p:/data/**",recursive=True)
['p:/data\\', 'p:/data\\folder1', 'p:/data\\folder1\\text1.txt', 'p:/data\\folder2', 'p:/data\\folder2\\text2.txt', 'p:/data\\root.txt']
>>>
ファイルパスの操作(フォルダ名やファイル名の分割、結合)
指定したパスからファイル名、フォルダ名などを取り出すには次のメソッドを使います。
機能 | 記述例 | 説明 |
---|---|---|
パスをファイル名と拡張子に分けて取得 | os.path.splitext('path') | pathを拡張子とそれ以外に分割し、タプルで返す。 'e:/document/test.csv' ⇒ ('e:/document/text' , '.csv') 尚、拡張子にはピリオドが含まれる。 |
ファイル名のみ取得 | os.path.basename('path') | 'e:/document/test.csv' ⇒ 'test.csv' |
フォルダ名の取得 | os.path.dirname('path') | 'e:/document/test.csv' ⇒ 'e:/document' |
フォルダ名と ファイル名を結合 | os.path.join('folder1','folder2',','filename') | 'e:/data','sub','file.txt' ⇒ 'e:/data/sub/file.txt |
相対バスの取得 | os.path.relpath('targetpath','basetpath') | targetpathで指定されたパスを、basepathを基準とした相対パスに変換する。 'd:/aaa/file.txt' , 'd:/bbb/test.txt' ⇒ '../../aaa/file.txt' |
ファイルサイズ の取得 | os.path.getsize('path') | ファイルサイズをバイト数で返す。 |
作成時刻の取得 | os.path.getctime('path') | いずれも1970年1月1日午前0時0分0秒からの経過秒を返すため、datetime.datetime.formtimestamp() を使って年月日時分秒に変換する必要あり。 |
更新時刻の取得 | os.path.getmtime('path') | |
最終アクセス時刻 の取得 | os.path.getatime('path') |
# パスをファイル名と拡張子に分割(拡張子にピリオドを含む)
filename, ext = os.path.splitext('e:/document/test.csv')
# パスからファイル名のみ抽出
filename = os.path.basename('e:/document/test.csv')
# パスからディレクトリ名(フォルダ名)を抽出
dirname = os.path.dirname('e:/document/test.csv')
# 複数のパス要素を結合して新しいパスを作成
path = os.path.join('e:/data', 'sub', 'file.txt')
# 指定されたパスを、別のパスを基準とした相対パスに変換
rel_path = os.path.relpath("d:/aaa/file.txt", "d:/bbb")
# ファイルのサイズをバイト数で取得
size = os.path.getsize("file.txt")
# ファイルの作成時刻を取得(1970年1月1日からの経過秒)
ctime = os.path.getctime("file.txt")
# ファイルの更新時刻を取得(1970年1月1日からの経過秒)
mtime = os.path.getmtime("file.txt")
# ファイルの最終アクセス時刻を取得(1970年1月1日からの経過秒)
atime = os.path.getatime("file.txt")
# 経過秒を年月日時分秒に変換
import time
create_time = time.ctime(ctime) # 経過秒を人間が読みやすい形式に変換
>>> import os
>>>import datetime
>>>access_time = os.path.getatime("P:/hoge.txt")
>>># タイムスタンプから日時に変換
>>>access_time_str = datetime.datetime.fromtimestamp(access_time)
>>>print("アクセス日時:", access_time_str)
>>>
>>>アクセス日時: 2024-02-03 15:11:50.395828
ファイル、フォルダ操作の実用事例
ワイルドカードに一致するファイルを対象にフォルダを丸ごとコピー
copy_files_with_filter関数は、source_folderで指定されたフォルダ階層を丸ごとdestination_folder で指定されたフォルダ直下にコピーする関数です。
copy_files_with_filter(source_folder, destination_folder ,wildcard_pattern,filter_func=None)
特徴としては、第3引数にワイルドカードが指定でき、かつ コールバック関数 filter_func によりフィルター条件が掛けられる点です。
import os
import shutil
import glob
import datetime
def copy_files_with_filter(source_folder, destination_folder ,wildcard_pattern,filter_func=None):
"""
指定したフォルダ階層ごと、ワイルドカードに一致し、フィルタ関数に一致するファイルを別の階層にコピーします。
Args:
source_folder (str): コピー元のフォルダのパス。
wildcard_pattern (str): ワイルドカードパターン。
destination_folder (str): コピー先のフォルダのパス。
filter_func (function, optional): フィルタ条件を指定するコールバック関数。デフォルトは None。
Returns:
None
"""
# ワイルドカードに一致するファイルを取得
matching_files = glob.glob(os.path.join(source_folder, '**', wildcard_pattern), recursive=True)
# コピー先のフォルダ構造を再現しながらファイルをコピーする
for file_path in matching_files:
# フィルタ関数が指定されており、その関数がFalseを返す場合はスキップする
if filter_func is not None and not filter_func(file_path):
continue
# コピー元のファイルパスを取得
file_relative_path = os.path.relpath(file_path, source_folder)
destination_path = os.path.join(destination_folder, file_relative_path)
# コピー先のディレクトリを作成
os.makedirs(os.path.dirname(destination_path), exist_ok=True)
# ファイルをコピー
shutil.copy(file_path, destination_path)
print(f'{file_path} を {destination_path} にコピーしました。')
次のソースコードはコールバック関数のサンプルです。コピー対象となるファイルのファイルスタンプが、引数で指定した日時より大きい場合は True 、以下の場合は False を返します。これをcopy_files_with_filterの第4引数に指定することで、指定日時より以降に作成されたファイルのみコピーすることができます。
def is_newer_than(date_str):
"""
指定された日付よりも新しいファイルをフィルタリングするためのコールバック関数。
Args:
date_str (str): 日付を表す文字列。形式は 'YYYY-MM-DD HH:MM:SS'。
Returns:
function: コールバック関数。
"""
# 日付文字列を datetime オブジェクトに変換
specified_date = datetime.datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S')
def filter_func(file_path):
# ファイルの最終更新時刻を取得し、指定された日付よりも新しいかどうかを確認
file_mtime = os.path.getmtime(file_path)
file_mtime_datetime = datetime.datetime.fromtimestamp(file_mtime)
return file_mtime_datetime > specified_date
return filter_func
関数の使い方は次の通りです。
copy_files_with_filter("o:/images","p:/data/images2","*.jpeg",is_newer_than("2024-02-03 19:00:00"))
ちなみに、is_newer_thanの戻り値としてコールバック関数のfilter_funcを返していますが、コピー対象の日時も関数の中で処理したかったからです。
もし関数外でspecified_date を定義するのであれば、次の様に書けます。
specified_date = datetime.datetime.strptime("2024-02-03 10:00:00", '%Y-%m-%d %H:%M:%S')
def filter_func(file_path):
# ファイルの最終更新時刻を取得し、指定された日付よりも新しいかどうかを確認
file_mtime = os.path.getmtime(file_path)
file_mtime_datetime = datetime.datetime.fromtimestamp(file_mtime)
return file_mtime_datetime > specified_date
copy_files_with_filter("o:/images","p:/data/images2","*.jpeg",filter_func)
2つのフォルダを比較して、それぞれの差分を抽出する
folder_diff関数は、source_folderで指定されたフォルダと、target_folderで指定フォルダに対して、階層下にある全てのファイルを比較し、それぞれの差分をタプルで返します。
unique_source_files,unique_target_files = folder_diff(source_folder, target_folder ,wildcard_pattern)
戻り値は、元のフォルダにのみ存在するファイル(unique_source_files)のリストと、比較先のフォルダにのみ存在するファイル(unique_target_files)のリストです。
import os
import glob
def folder_diff(source_folder, target_folder, wildcard_pattern='*'):
"""
指定された2つのフォルダ間の差分を抽出します。
Args:
source_folder (str): 比較元のフォルダのパス。
target_folder (str): 比較先のフォルダのパス。
wildcard (str): ワイルドカード文字列(デフォルトは '*')。
Returns:
tuple: (比較元にのみ存在するファイルのリスト, 比較先にのみ存在するファイルのリスト)。
"""
# 比較元フォルダ内のファイルを取得
source_files = set(glob.glob(os.path.join(source_folder, '**', wildcard_pattern), recursive=True))
# 比較先フォルダ内のファイルを取得
target_files = set(glob.glob(os.path.join(target_folder, '**', wildcard_pattern), recursive=True))
# 取得したファイルパスをsource_folderを基準とした相対パスに変換
source_files = {os.path.relpath(file, source_folder) for file in source_files}
# 取得したファイルパスをtarget_folderを基準とした相対パスに変換
target_files = {os.path.relpath(file, target_folder) for file in target_files}
# 比較元にのみ存在するファイルを抽出
unique_source_files = source_files - target_files
# 比較先にのみ存在するファイルを抽出
unique_target_files = target_files - source_files
# 結果を返す
return list(unique_source_files), list(unique_target_files)
使い方は次の通りです。
source_folder = 'O:/images'
target_folder = 'P:/data/images2'
unique_source_files, unique_target_files = folder_diff(source_folder, target_folder)
print("比較元にのみ存在するファイル:", unique_source_files)
print("比較先にのみ存在するファイル:", unique_target_files)
まとめ
今回は、Pythonでファイルやフォルダを扱うためのライブラリである os、shutil、glob について、それぞれの特徴と役割を明確にし、用途別(ファイル一覧の取得、コピー、移動、削除など)にメソッドを紹介しました。
また、実用事例として、ワイルドカードに対応したフォルダ丸ごとコピー関数や、2つのフォルダの差分ファイル抽出関数について、サンプルコードと使い方を解説しました。
趣味でプログラミングをする際に、パソコン上のファイルを扱うことも多いでしょう。そんな時に、今回の記事を参考にしていただければ幸いです。
コメント