Python globでファイル検索!ワイルドカードの基本と使い方を徹底解説
はじめに:ファイル検索の重要性とglob
モジュールの役割
現代のデジタル環境において、ファイルはあらゆる情報の基盤です。ソフトウェア開発、データ分析、システム管理、Webコンテンツの生成など、多岐にわたるタスクで特定のファイルを効率的に見つけ出す能力は不可欠です。例えば、プロジェクト内のすべてのPythonスクリプトをリストアップしたい、特定の命名規則に従ったログファイルを処理したい、あるいは特定の拡張子を持つ画像ファイルだけをコピーしたいといったシナリオは日常的に発生します。
Pythonの標準ライブラリに組み込まれているglob
モジュールは、このようなファイル検索のニーズにシンプルかつ強力な方法で応えます。glob
は、UNIXシェルなどでお馴染みの「ワイルドカード」(パターンマッチング)を使用して、ディレクトリ内のファイルやフォルダを検索する機能を提供します。正規表現ほど複雑な記述は不要で、直感的で分かりやすい記述で目的のファイルを特定できるのが最大の魅力です。
本記事では、Pythonのglob
モジュールを徹底的に掘り下げ、その基本的な使い方から、ワイルドカードの種類と適用例、さらにglob.glob()
とglob.iglob()
の違い、pathlib
モジュールとの連携、そして実用的な応用例や注意点に至るまで、約5000語にわたって詳細に解説します。この記事を読み終える頃には、あなたはglob
を自在に操り、どんなファイル検索のタスクも効率的にこなせるようになるでしょう。
1. glob
モジュールとは?:シンプルで強力なファイルパス検索ツール
glob
モジュールは、指定されたパターンに一致するすべてのパス名を見つけるためのPython標準ライブラリです。このモジュール名は、UNIXシェルがパス名展開(”globbing”)を実行する方法に由来しています。シェルでls *.txt
と入力すると、カレントディレクトリのすべてのテキストファイルが表示されるように、glob
モジュールも同様の機能を提供します。
従来のファイル検索方法との比較
Pythonでファイルやディレクトリを検索する方法はいくつかあります。
-
os.listdir()
と手動フィルタリング:
最も基本的な方法ですが、ディレクトリ内のすべてのエントリを取得し、その中から目的のファイルを自分でフィルタリングする必要があります。ファイル名が複雑な場合や、特定のパターンに一致するものを探す場合には、正規表現(re
モジュール)と組み合わせる必要があり、コードが煩雑になりがちです。“`python
import os
import re例: カレントディレクトリのすべてのテキストファイルを検索
files = os.listdir(‘.’)
txt_files = [f for f in files if re.match(r’.*.txt$’, f)]
print(txt_files)
“` -
os.walk()
:
ディレクトリツリーを再帰的に走査するための強力なツールです。特定のディレクトリとそのサブディレクトリ内のすべてのファイルとフォルダを効率的に処理できます。しかし、os.walk()
自体はパターンマッチング機能を持たないため、結果をフィルタリングするためにはやはり追加のロジック(fnmatch
やre
など)が必要です。“`python
import os例: ディレクトリツリー内のすべてのテキストファイルを検索
txt_files = []
for root, dirs, files in os.walk(‘.’):
for file in files:
if file.endswith(‘.txt’):
txt_files.append(os.path.join(root, file))
print(txt_files)
“` -
glob
モジュール:
ここでglob
の出番です。glob
は、ワイルドカード(*
,?
,[]
,**
)を使って、直接的にパターンに一致するパスをリストアップします。これにより、コードがはるかに簡潔になり、直感的に理解できます。特に、シェルスクリプトの経験がある開発者にとっては、その記述方法が非常に馴染みやすいでしょう。“`python
import glob例: カレントディレクトリのすべてのテキストファイルを検索
txt_files = glob.glob(‘*.txt’)
print(txt_files)例: サブディレクトリを含むすべてのテキストファイルを検索 (Python 3.5+)
all_txt_files = glob.glob(‘*/.txt’, recursive=True)
print(all_txt_files)
“`
このように、glob
は特定のパターンに一致するパス名を取得する際に、最も手軽で読みやすい選択肢となります。
2. ワイルドカードの基本:パターンマッチングの鍵
glob
モジュールは、以下の基本的なワイルドカードをサポートしています。これらはシェルスクリプトで使われるワイルドカードとほぼ同じ意味を持ちます。
2.1. *
(アスタリスク):0個以上の任意の文字にマッチ
最も頻繁に使われるワイルドカードです。*
は、0個以上の任意の文字(ディレクトリ区切り文字/
や\
を除く)にマッチします。
使用例:
*.txt
: 拡張子が.txt
のすべてのファイルにマッチします。- 例:
document.txt
,report.txt
,data.txt
- 例:
data_*
: ファイル名がdata_
で始まるすべてのファイルやディレクトリにマッチします。- 例:
data_01.csv
,data_analysis
,data_final.xlsx
- 例:
temp/*.log
:temp
ディレクトリ内の拡張子が.log
のすべてのファイルにマッチします。- 例:
temp/server.log
,temp/application_error.log
- 例:
注意点:
*
はデフォルトではディレクトリ区切り文字(/
や\
)にはマッチしません。したがって、dir*/*.txt
はdir
で始まるディレクトリ直下の.txt
ファイルにマッチしますが、dir/subdir/file.txt
のようなパスにはマッチしません。後者のような再帰的な検索には後述の**
を使用します。
2.2. ?
(クエスチョンマーク):1個の任意の文字にマッチ
?
は、ちょうど1個の任意の文字にマッチします。特定の桁数や文字数のパターンを持つファイルを探す場合に便利です。
使用例:
file?.log
:file
の後に任意の1文字が続き、その後に.log
が続くファイルにマッチします。- 例:
file1.log
,fileA.log
- マッチしない例:
file.log
(0文字),file10.log
(2文字)
- 例:
report_??.pdf
:report_
の後に任意の2文字が続き、その後に.pdf
が続くファイルにマッチします。- 例:
report_01.pdf
,report_XY.pdf
- 例:
2.3. []
(角括弧):文字の集合にマッチ
[]
は、角括弧内に指定された文字のいずれか1文字にマッチします。範囲指定や否定も可能です。
使用例:
[abc].txt
:a
,b
, またはc
のいずれか1文字がファイル名の先頭にある.txt
ファイルにマッチします。- 例:
a.txt
,b.txt
,c.txt
- 例:
[0-9].log
:0
から9
までの数字のいずれか1文字がファイル名の先頭にある.log
ファイルにマッチします。- 例:
1.log
,5.log
,9.log
- 例:
[a-zA-Z].csv
: 小文字または大文字の英字のいずれか1文字がファイル名の先頭にある.csv
ファイルにマッチします。- 例:
A.csv
,z.csv
- 例:
[!abc].md
:a
,b
,c
以外の任意の1文字がファイル名の先頭にある.md
ファイルにマッチします。- 例:
d.md
,1.md
,X.md
(ただし、a.md
,b.md
,c.md
はマッチしません)
- 例:
注意点:
角括弧内で特殊な意味を持つ文字(ハイフン-
、角括弧[
や]
)をリテラルとして扱いたい場合は、それらをエスケープする必要があります。ただし、glob
では正規表現のようなバックスラッシュ\
でのエスケープはサポートしていません。代わりに、リテラルとして扱いたい文字を[]
で囲むことでエスケープします。
* [[]
: リテラルの[
にマッチ
* []]
: リテラルの]
にマッチ
* [!]
: リテラルの!
にマッチ
* [ -]
: リテラルのハイフン-
にマッチ(これは範囲指定ではないことを示す)
例えば、ファイル名に[
が含まれるファイルを探す場合、file[[.txt
のように記述します。ただし、このようなケースは稀であり、通常はリテラル文字をそのまま記述できます。
2.4. **
(二重アスタリスク):ディレクトリを跨いだ再帰検索 (Python 3.5+)
**
は、Python 3.5以降で追加された非常に便利な機能で、recursive=True
オプションと組み合わせることで、0個以上のディレクトリとサブディレクトリに再帰的にマッチします。これにより、ディレクトリツリー全体からファイルを検索できるようになります。
使用例:
**/*.py
: カレントディレクトリとそのすべてのサブディレクトリにある、拡張子が.py
のすべてのファイルにマッチします。- 例:
main.py
,src/utils.py
,tests/unit/test_data.py
- 例:
data/**/report_*.txt
:data
ディレクトリ以下にある、任意の深さのサブディレクトリ内のreport_
で始まる.txt
ファイルにマッチします。- 例:
data/2023/report_final.txt
,data/temp/backup/report_summary.txt
- 例:
**/assets/**/*.png
: 任意のディレクトリの直下にあるassets
ディレクトリの中から、そのサブディレクトリを含めてすべての.png
ファイルを探します。- 例:
project/images/assets/icon.png
,project/design/assets/temp/logo.png
- 例:
使用上の注意:
**
を使用すると、大規模なファイルシステムを検索する際に、かなりの時間とシステムリソースを消費する可能性があります。特に、ドライブのルートディレクトリなど、広範な範囲で**
を使う場合は注意が必要です。必要最小限の範囲で使うか、後述するglob.iglob()
を使ってメモリ効率を考慮することをお勧めします。
3. glob.glob()
関数の詳細:基本と実践
glob
モジュールの中核となる関数はglob.glob()
です。この関数は、指定されたパターンに一致するパスのリストを返します。
3.1. 基本的な使い方
glob.glob(pathname, *, recursive=False)
pathname
: 検索対象となるワイルドカードを含む文字列パターンです。絶対パス、相対パスのどちらも指定できます。recursive
: オプション引数です。True
に設定すると、**
ワイルドカードが有効になり、再帰的なディレクトリ検索が可能になります。デフォルトはFalse
です。
返り値:
マッチしたパス文字列のリストを返します。マッチするパスが一つもない場合は、空のリストが返されます。リスト内のパスの順序は保証されません(OSやファイルシステムの特性に依存する場合があります)。通常はsorted()
関数を使って結果をソートすることが推奨されます。
コード例:
“`python
import glob
import os
サンプルファイルとディレクトリの作成
実際の実行時には、この部分をスキップして既存のディレクトリで試すことも可能です
if not os.path.exists(“test_data”):
os.makedirs(“test_data/docs”)
os.makedirs(“test_data/images”)
os.makedirs(“test_data/reports/2023”)
with open(“test_data/doc1.txt”, “w”) as f: f.write(“content”)
with open(“test_data/doc2.txt”, “w”) as f: f.write(“content”)
with open(“test_data/docs/summary.txt”, “w”) as f: f.write(“content”)
with open(“test_data/docs/README.md”, “w”) as f: f.write(“content”)
with open(“test_data/images/photo.jpg”, “w”) as f: f.write(“content”)
with open(“test_data/reports/monthly_report.xlsx”, “w”) as f: f.write(“content”)
with open(“test_data/reports/2023/annual_report.pdf”, “w”) as f: f.write(“content”)
with open(“test_data/temp.log”, “w”) as f: f.write(“content”)
with open(“test_data/.hidden_file”, “w”) as f: f.write(“content”) # 隠しファイル
print(“— 1. カレントディレクトリ直下の.txtファイル —“)
test_data/doc1.txt, test_data/doc2.txt
results = glob.glob(‘test_data/*.txt’)
print(sorted(results)) # 順序は保証されないためソート
print(“\n— 2. test_dataディレクトリ内のすべてのファイル/ディレクトリ —“)
test_data/doc1.txt, test_data/doc2.txt, test_data/docs, test_data/images, etc.
results = glob.glob(‘test_data/*’)
print(sorted(results))
print(“\n— 3. test_dataディレクトリ直下の隠しファイル (通常マッチしない) —“)
Windowsでは*
でマッチする場合があるが、UNIX系では通常マッチしない
results = glob.glob(‘test_data/.*’) # ドットファイルには明示的にドットを記述
print(sorted(results))
print(“\n— 4. test_data/docsディレクトリ内のMDファイル —“)
test_data/docs/README.md
results = glob.glob(‘test_data/docs/*.md’)
print(sorted(results))
print(“\n— 5. test_dataディレクトリ以下、任意の場所にあるTXTファイル (recursive=True) —“)
test_data/doc1.txt, test_data/doc2.txt, test_data/docs/summary.txt
results = glob.glob(‘test_data/*/.txt’, recursive=True)
print(sorted(results))
print(“\n— 6. test_dataディレクトリ以下、任意の場所にあるjpgまたはpdfファイル —“)
test_data/images/photo.jpg, test_data/reports/2023/annual_report.pdf
results_jpg = glob.glob(‘test_data//*.jpg’, recursive=True)
results_pdf = glob.glob(‘test_data//*.pdf’, recursive=True)
print(sorted(results_jpg + results_pdf)) # 複数のパターンを組み合わせる例
print(“\n— 7. test_data/reports/2023ディレクトリ内のファイル —“)
test_data/reports/2023/annual_report.pdf
results = glob.glob(‘test_data/reports/2023/*’)
print(sorted(results))
print(“\n— 8. ファイル名が ‘doc’ で始まり、1文字のワイルドカードを含むファイル —“)
test_data/doc1.txt, test_data/doc2.txt
results = glob.glob(‘test_data/doc?.txt’)
print(sorted(results))
print(“\n— 9. ‘reports’ディレクトリ内のExcelまたはPDFファイル —“)
test_data/reports/monthly_report.xlsx, test_data/reports/2023/annual_report.pdf
results_xls_pdf = glob.glob(‘test_data/reports/*/.{xlsx,pdf}’, recursive=True)
print(sorted(results_xls_pdf)) # 複数の拡張子を検索する際は{}は使えないので別途対応が必要 (後述)
クリーンアップ (オプション)
import shutil
shutil.rmtree(“test_data”)
“`
3.2. 絶対パスと相対パスの指定
pathname
引数には、絶対パスでも相対パスでも指定できます。
- 相対パス: 現在の作業ディレクトリを基準とします。上記例のほとんどは相対パスです。
- 絶対パス: ファイルシステムのルートから始まる完全なパスです。
- 例:
C:/Users/YourName/Documents/*.txt
(Windows) - 例:
/home/yourname/data/**/*.csv
(Linux/macOS)
- 例:
絶対パスを使用する場合、OSのパス区切り文字(Windowsでは\
、UNIX系では/
)に注意が必要です。Pythonは内部的にこれらの違いを吸収しますが、パターン文字列を記述する際は、一貫して/
を使うことを推奨します。PythonはOSに依存せず正しく解釈します。
3.3. マッチしない場合
glob.glob()
は、指定されたパターンに一致するパスが存在しない場合、空のリスト[]
を返します。エラーは発生しません。
“`python
import glob
results = glob.glob(‘non_existent_dir/*.xyz’)
print(f”マッチするファイルはありません: {results}”) # 出力: マッチするファイルはありません: []
“`
4. glob.iglob()
関数の詳細:メモリ効率の良い検索
glob.iglob()
は、glob.glob()
と同様の機能を提供しますが、マッチしたパスのリストを一度にすべて返すのではなく、イテレータを返します。これにより、大規模なファイルシステムを検索する際に、メモリ効率が大幅に向上します。
4.1. ジェネレータ(イテレータ)を返すメリット
- メモリ効率:
glob.glob()
はすべての結果をメモリにロードしてからリストとして返しますが、glob.iglob()
は結果を1つずつ「生成」します。これにより、数十万、数百万といった多数のファイルがマッチする場合でも、メモリを大量に消費することなく処理を進めることができます。 - 即時性: 最初の結果が利用可能になり次第すぐに処理を開始できます。すべての結果が揃うのを待つ必要がありません。これは、特に巨大な検索空間で最初の数個の結果だけが必要な場合に有利です。
4.2. 使い方とglob.glob()
との使い分け
glob.iglob(pathname, *, recursive=False)
引数はglob.glob()
と全く同じです。
コード例:
“`python
import glob
import os
サンプルファイルの作成 (glob.globの例と同じ)
if not os.path.exists(“test_data”):
os.makedirs(“test_data/docs”)
os.makedirs(“test_data/images”)
os.makedirs(“test_data/reports/2023”)
with open(“test_data/doc1.txt”, “w”) as f: f.write(“content”)
with open(“test_data/doc2.txt”, “w”) as f: f.write(“content”)
with open(“test_data/docs/summary.txt”, “w”) as f: f.write(“content”)
with open(“test_data/docs/README.md”, “w”) as f: f.write(“content”)
with open(“test_data/images/photo.jpg”, “w”) as f: f.write(“content”)
with open(“test_data/reports/monthly_report.xlsx”, “w”) as f: f.write(“content”)
with open(“test_data/reports/2023/annual_report.pdf”, “w”) as f: f.write(“content”)
with open(“test_data/temp.log”, “w”) as f: f.write(“content”)
print(“— glob.iglob() を使った例 —“)
test_dataディレクトリ以下のすべてのテキストファイルを検索
結果はリストではなくイテレータとして得られる
text_files_iterator = glob.iglob(‘test_data/*/.txt’, recursive=True)
print(“イテレータの要素を一つずつ処理:”)
for filepath in text_files_iterator:
print(filepath)
print(“\n— イテレータは一度しか消費できないことを示す —“)
イテレータは一度消費されると空になる
for filepath in text_files_iterator:
print(f”この行は表示されません: {filepath}”) # 既に消費されているため何も表示されない
print(“\n— イテレータをリストに変換して結果を確認 —“)
必要に応じてlist()でリストに変換することも可能だが、その場合iglobのメモリメリットは失われる
text_files_list = list(glob.iglob(‘test_data/*/.txt’, recursive=True))
print(sorted(text_files_list))
クリーンアップ (オプション)
import shutil
shutil.rmtree(“test_data”)
“`
使い分けの基準:
glob.glob()
:- 検索結果の数が比較的少ない(数百〜数千程度まで)場合。
- すべての結果を一度にメモリにロードして、リスト操作(ソート、フィルタリング、スライスなど)を頻繁に行いたい場合。
- コードのシンプルさを最優先する場合。
glob.iglob()
:- 検索結果の数が非常に多い可能性がある(数万、数十万以上)場合。
- 結果を1つずつ処理し、メモリ使用量を抑えたい場合。
- 最初のいくつかの結果だけを素早く見つけたい場合や、検索を途中で停止できる可能性がある場合。
一般的に、大規模なファイルシステムを扱う場合はiglob()
を優先的に検討し、それ以外の場合はglob()
で十分という認識で良いでしょう。
5. 具体的な使用例と応用:glob
を実用的に活用する
ここでは、glob
モジュールを使ったより具体的なシナリオと、他のモジュールと組み合わせた応用例を紹介します。
5.1. 特定の拡張子のファイル検索
最も一般的なユースケースです。
“`python
import glob
すべてのCSVファイルを検索
csv_files = glob.glob(‘data/*.csv’)
print(f”CSVファイル: {sorted(csv_files)}”)
複数の拡張子を同時に検索 (それぞれのglobを結合)
image_files = glob.glob(‘images/.jpg’) + \
glob.glob(‘images/.png’) + \
glob.glob(‘images/*.gif’)
print(f”画像ファイル: {sorted(image_files)}”)
または、リスト内包表記とany()を使ってフィルタリング
all_files = glob.glob(‘images/*’)
supported_extensions = (‘.jpg’, ‘.png’, ‘.gif’)
filtered_images = [f for f in all_files if f.endswith(supported_extensions)]
print(f”フィルタリングされた画像ファイル: {sorted(filtered_images)}”)
再帰的に検索する場合
all_documents = glob.glob(‘project//*.txt’, recursive=True) + \
glob.glob(‘project//.md’, recursive=True)
print(f”すべてのドキュメント: {sorted(all_documents)}”)
``
{}
**TIPS**:を使った複数拡張子指定はシェルではよく見られますが、Pythonの
globでは直接サポートされていません(例:
.{txt,csv}は機能しない)。そのため、上記のように複数の
glob.glob()呼び出しを結合するか、一度
*で全て取得した後に
endswith()`などでフィルタリングする必要があります。
5.2. 特定のプレフィックス/サフィックスを持つファイル検索
ファイル名の開始や終了部分が特定のパターンに従っている場合。
“`python
import glob
‘log_’で始まるログファイルを検索
log_files = glob.glob(‘logs/log_*.log’)
print(f”特定プレフィックスのログ: {sorted(log_files)}”)
日付形式のログファイル (例: error_2023_10_26.log)
‘error_YYYY_MM_DD.log’
daily_errors = glob.glob(‘logs/error_????????.log’)
print(f”日付別エラーログ: {sorted(daily_errors)}”)
“`
5.3. 連番ファイルや日付ファイルの検索
ログファイルやバックアップファイルなどでよく見られる連番や日付を含むファイルを検索します。
“`python
import glob
series_001.txt, series_002.txt のような連番ファイル
series_files = glob.glob(‘output/series_???.txt’)
print(f”連番ファイル: {sorted(series_files)}”)
2023年10月のレポートファイル (例: report_202310_data.csv)
monthly_reports = glob.glob(‘reports/report_202310_*.csv’)
print(f”月次レポート: {sorted(monthly_reports)}”)
“`
5.4. ファイルとディレクトリの区別
glob
はファイルとディレクトリの両方を返します。必要に応じてos.path
モジュールで区別します。
“`python
import glob
import os
仮のディレクトリとファイルを作成
os.makedirs(“my_dir/subdir”, exist_ok=True)
with open(“my_dir/file.txt”, “w”) as f: f.write(“test”)
with open(“my_dir/another_file.log”, “w”) as f: f.write(“test”)
all_items = glob.glob(‘my_dir/*’)
files = [item for item in all_items if os.path.isfile(item)]
dirs = [item for item in all_items if os.path.isdir(item)]
print(f”すべてのアイテム: {sorted(all_items)}”)
print(f”ファイルのみ: {sorted(files)}”)
print(f”ディレクトリのみ: {sorted(dirs)}”)
再帰的に検索し、ファイルとディレクトリを区別
all_items_recursive = glob.glob(‘my_dir/*/‘, recursive=True)
files_recursive = [item for item in all_items_recursive if os.path.isfile(item)]
dirs_recursive = [item for item in all_items_recursive if os.path.isdir(item)]
print(f”再帰的なファイル: {sorted(files_recursive)}”)
print(f”再帰的なディレクトリ: {sorted(dirs_recursive)}”)
“`
5.5. ファイルサイズ、更新日時によるフィルタリング
os.path.getsize()
やos.path.getmtime()
(更新日時)、datetime
モジュールを組み合わせて、さらに詳細なフィルタリングが可能です。
“`python
import glob
import os
import datetime
仮のファイルをいくつか作成
for i in range(1, 6):
with open(f”temp_file_{i}.txt”, “w”) as f:
f.write(“a” * (i * 100)) # ファイルサイズを変える
os.utime(“temp_file_1.txt”, (os.stat(“temp_file_1.txt”).st_atime, os.time() – 86400 * 5)) # 5日前
os.utime(“temp_file_2.txt”, (os.stat(“temp_file_2.txt”).st_atime, os.time() – 86400 * 2)) # 2日前
100バイトより大きいテキストファイルを検索
large_txt_files = []
for f in glob.glob(‘temp_file_*.txt’):
if os.path.isfile(f) and os.path.getsize(f) > 100:
large_txt_files.append(f)
print(f”100バイトより大きいファイル: {sorted(large_txt_files)}”)
過去3日以内に更新されたファイルを検索
three_days_ago = datetime.datetime.now() – datetime.timedelta(days=3)
recent_files = []
for f in glob.glob(‘temp_file_*.txt’):
if os.path.isfile(f):
mtime_timestamp = os.path.getmtime(f)
mtime_datetime = datetime.datetime.fromtimestamp(mtime_timestamp)
if mtime_datetime > three_days_ago:
recent_files.append(f)
print(f”過去3日以内に更新されたファイル: {sorted(recent_files)}”)
“`
5.6. パターン文字列の動的生成
f-stringなどを使って、日付やユーザー入力に基づいてパターンを動的に生成できます。
“`python
import glob
import datetime
today = datetime.date.today()
today_str = today.strftime(‘%Y%m%d’) # 例: 20231026
今日の日付を含むログファイルを検索
pattern = f’logs/app_{today_str}_*.log’
today_logs = glob.glob(pattern)
print(f”今日のログファイル ({pattern}): {sorted(today_logs)}”)
ユーザー入力によるファイル名検索
user_input = input(“検索したいファイル名の一部を入力してください: “)
search_pattern = f’data/{user_input}.csv’
user_files = glob.glob(search_pattern)
print(f”検索結果 ({search_pattern}): {sorted(user_files)}”)
“`
6. glob
の注意点と限界:知っておくべきこと
glob
は非常に便利ですが、いくつかの注意点と限界があります。
6.1. 隠しファイル(ドットファイル)の扱い
UNIX/Linux系システムでは、ファイル名の先頭が.
(ドット)で始まるファイルは「隠しファイル」として扱われます(例: .bashrc
, .gitignore
)。Windowsではファイル属性で隠しファイルが設定されます。
glob
の*
ワイルドカードは、これらの隠しファイルにはデフォルトではマッチしません。隠しファイルを含めるには、明示的に.*
のようなパターンを含める必要があります。
“`python
import glob
import os
仮の隠しファイルを作成
with open(“.env”, “w”) as f: f.write(“test”)
with open(“config/.hidden_config”, “w”) as f: f.write(“test”)
‘*’ではマッチしない
all_files = glob.glob(‘‘)
print(f”通常のワイルドカード (‘‘) でマッチするファイル: {sorted(all_files)}”)
明示的に’.*’を含める必要がある
hidden_files = glob.glob(‘.‘)
print(f”隠しファイル (‘.‘) でマッチするファイル: {sorted(hidden_files)}”)
再帰検索で隠しファイルを含める場合
**/.gitのようなパスは.git
がディレクトリ名なので、.
から始まっていてもマッチする場合があります。
ただし、ファイル名としては.env
のようなものは明示的に指定しないとマッチしません。
例えば **/*
は .env
にはマッチしないが **/README.md
にはマッチします。
**/.env
のようにフルパスで指定すればマッチします。
“`
6.2. 大文字・小文字の区別
ファイル名の大文字・小文字を区別するかどうかは、実行環境のOSやファイルシステムに依存します。
- Windows: 通常、ファイルシステム(NTFS)は大文字・小文字を区別しません。
*.txt
はfile.txt
もFILE.TXT
もマッチします。 - Linux/macOS: 通常、ファイルシステム(Ext4, APFSなど)は大文字・小文字を区別します。
*.txt
はfile.txt
にはマッチしますが、FILE.TXT
にはマッチしません。
クロスプラットフォームなコードを書く場合は、結果をすべて小文字(または大文字)に変換して比較するなどの対策が必要になることがあります。
“`python
import glob
import os
OSによる挙動の違い
Windows: ‘Test.txt’と’test.txt’は同じファイルとみなされることが多い
Linux: ‘Test.txt’と’test.txt’は別のファイル
with open(“MyFile.TXT”, “w”) as f: f.write(“test”)
results = glob.glob(‘myfile.txt’)
print(f”‘myfile.txt’の検索結果: {sorted(results)}”)
results_case_insensitive = [f for f in glob.glob(‘*’) if f.lower().endswith(‘.txt’)]
print(f”大文字小文字を無視して.txtを検索: {sorted(results_case_insensitive)}”)
“`
6.3. シンボリックリンクの扱い
glob
はデフォルトでシンボリックリンクを追跡します。つまり、シンボリックリンクが指す実際のパスを検索し、その先のファイルがパターンに一致すれば結果に含めます。これは、無限ループを引き起こす可能性はありますが、glob
自体がシンボリックリンクの循環参照を特別に検出し、無限ループに陥ることは稀です。ただし、パフォーマンスに影響を与える可能性はあります。
6.4. パフォーマンスの考慮事項
**
(再帰検索): 大規模なディレクトリツリーに対して**
を使用すると、すべてのサブディレクトリを走査するため、検索に時間がかかります。特にネットワークドライブやSSDではないHDD上の大量のファイルに対しては顕著です。- パターンの一致効率: パターンの先頭に具体的なパスを指定する(例:
data/input/*.csv
)ことで、検索範囲を限定し、効率を高めることができます。*.csv
のようにパターンが広すぎると、不必要なディレクトリをすべて走査する必要が生じることがあります。 iglob()
の活用: 前述の通り、結果の数が多くなる可能性がある場合は、glob.iglob()
を使用してメモリ効率と即時性を向上させましょう。
6.5. 正規表現との比較と使い分け
glob
はシェルスタイルのワイルドカードを提供し、シンプルさが魅力ですが、正規表現(re
モジュール)ほど強力ではありません。
-
glob
の得意なこと:- シンプルなファイル名、拡張子、ディレクトリ階層でのパターンマッチング。
- シェルコマンドに慣れているユーザーにとって直感的。
- パスの存在確認とリストアップが主目的。
-
glob
の苦手なこと(正規表現の得意なこと):- OR条件 (
|
) や繰り返し (+
,{}
) などの複雑なパターン。 - 行の先頭/末尾 (
^
,$
)、特定の文字セットではない文字 (\D
,\s
など)。 - ファイル名から特定の情報を抽出する(例:
(\d{4})-(\d{2})-(\d{2})_log.txt
から日付部分だけを取り出す)。 - 部分的な文字列の一致ではなく、厳密な構文チェック。
- OR条件 (
使い分けの指針:
* ファイルパスの列挙が主な目的で、パターンがシンプルならglob
。
* ファイル名や内容から特定のデータ抽出や、非常に複雑なパターンマッチングが必要ならre
。
* glob
で大まかにファイルを絞り込み、その後、各ファイル名に対してre
で詳細なマッチングを行うという組み合わせも有効です。
“`python
import glob
import re
globでCSVファイルを絞り込み
csv_files = glob.glob(‘data/*.csv’)
その中から、ファイル名に数字が含まれるCSVだけを正規表現で抽出
numbered_csv_files = [f for f in csv_files if re.search(r’\d’, f)]
print(f”数字を含むCSVファイル: {sorted(numbered_csv_files)}”)
“`
7. pathlib
モジュールとの連携:モダンPythonでのパス操作
Python 3.4で導入されたpathlib
モジュールは、ファイルシステムパスをオブジェクト指向的に扱うための強力なライブラリです。glob
の機能も内包しており、よりPythonicで堅牢なコードを書くことができます。
7.1. Path.glob()
とPath.rglob()
pathlib
のPath
オブジェクトは、独自のglob()
とrglob()
メソッドを提供します。これらは内部的にglob
モジュールを利用していますが、パス操作との一貫性があり、より洗練された記述が可能です。
Path.glob(pattern)
: 指定したパターンに一致する直下のファイル/ディレクトリのイテレータを返します。(recursive=False
相当)Path.rglob(pattern)
: 指定したパターンに一致する任意の深さのファイル/ディレクトリのイテレータを返します。(glob.glob(..., recursive=True)
相当)
コード例:
“`python
from pathlib import Path
import os
サンプルファイルの作成 (再掲)
if not os.path.exists(“test_data”):
os.makedirs(“test_data/docs”)
os.makedirs(“test_data/images”)
os.makedirs(“test_data/reports/2023”)
with open(“test_data/doc1.txt”, “w”) as f: f.write(“content”)
with open(“test_data/doc2.txt”, “w”) as f: f.write(“content”)
with open(“test_data/docs/summary.txt”, “w”) as f: f.write(“content”)
with open(“test_data/docs/README.md”, “w”) as f: f.write(“content”)
with open(“test_data/images/photo.jpg”, “w”) as f: f.write(“content”)
with open(“test_data/reports/monthly_report.xlsx”, “w”) as f: f.write(“content”)
with open(“test_data/reports/2023/annual_report.pdf”, “w”) as f: f.write(“content”)
with open(“test_data/temp.log”, “w”) as f: f.write(“content”)
カレントディレクトリをPathオブジェクトとして取得
current_dir = Path(‘.’)
print(f”カレントディレクトリ: {current_dir.resolve()}”)
print(“\n— Path.glob() を使った例 —“)
カレントディレクトリ直下のtest_dataディレクトリ内の.txtファイル
Path.glob() は Path オブジェクトを返す
for p in current_dir.glob(‘test_data/*.txt’):
print(p)
print(“\n— Path.rglob() を使った再帰検索 —“)
test_dataディレクトリ以下のすべての.txtファイル
for p in current_dir.rglob(‘test_data/*/.txt’): # recursive=True相当
print(p)
Pathオブジェクトの利点
is_file(), is_dir() で簡単に判別
for p in current_dir.rglob(‘test_data/*/‘):
if p.is_file():
print(f”ファイル: {p}”)
elif p.is_dir():
print(f”ディレクトリ: {p}”)
ファイル名や拡張子の取得も簡単
for p in current_dir.rglob(‘test_data/*/.jpg’):
print(f”ファイル名: {p.name}, 拡張子: {p.suffix}”)
クリーンアップ (オプション)
import shutil
shutil.rmtree(“test_data”)
“`
pathlib
を使用することで、パスの結合(/
演算子を使用)、ファイル属性の取得、存在確認など、様々なパス操作を統一されたオブジェクト指向インターフェースで行えるため、コードの可読性と保守性が向上します。新しいPythonプロジェクトでは、os.path
とglob
を個別に使うよりも、pathlib
を積極的に利用することが推奨されます。
8. ベストプラクティス:効率的で堅牢なglob
利用法
glob
を最大限に活用し、コードを堅牢にするためのベストプラクティスをいくつか紹介します。
-
結果は常にソートする:
glob.glob()
が返すリストの順序は保証されません。結果の再現性や予測可能性を確保するため、sorted()
関数で明示的にソートしましょう。python
import glob
files = sorted(glob.glob('data/*.csv')) -
recursive=True
の必要性を検討する:
安易にrecursive=True
を使用すると、不要な深さまで検索してしまい、パフォーマンスの低下や予期せぬ結果を招く可能性があります。必要な場合にのみ使用し、検索パスをできるだけ具体的に指定して範囲を絞りましょう。 -
大規模な検索では
iglob
を優先する:
数千個以上のファイルがマッチする可能性がある場合は、glob.iglob()
を使用してメモリ使用量を最適化し、ジェネレータの利点を活かしましょう。 -
OS間の互換性を考慮する:
- パス区切り文字は、パターン文字列では
/
で統一することをお勧めします。Pythonが適切に変換します。 - 隠しファイルや大文字・小文字の扱いはOSによって異なるため、クロスプラットフォーム対応が必要な場合は、これらの違いをコードで吸収するロジック(例:
filename.lower()
)を検討しましょう。 - パスの結合には
os.path.join
またはpathlib.Path
を使用し、OS依存のパス区切り文字の問題を回避しましょう。
“`python
import os
from pathlib import Pathos.path.join を使用したパス結合
base_dir = “my_project”
sub_dir = “src”
file_pattern = os.path.join(base_dir, sub_dir, “.py”) # “my_project/src/.py” または “my_project\src*.py”glob.glob(file_pattern)
pathlib を使用したパス結合
base_path = Path(“my_project”)
file_path_pattern = base_path / “src” / “*.py”list(file_path_pattern.glob())
“`
- パス区切り文字は、パターン文字列では
-
エラーハンドリングを考慮する:
glob
関数自体はファイルが見つからなくてもエラーになりませんが、検索したファイルパスを使って何か処理を行う際には、FileNotFoundError
やPermissionError
などが発生する可能性があります。適切なtry-except
ブロックでこれらのエラーを捕捉し、堅牢なコードを書きましょう。“`python
import glob
import osfor filepath in glob.glob(‘data/*.txt’):
try:
with open(filepath, ‘r’) as f:
content = f.read()
# ファイルの内容を処理
print(f”Read from {filepath}: {content[:20]}…”)
except FileNotFoundError:
print(f”エラー: {filepath} が見つかりません。”)
except PermissionError:
print(f”エラー: {filepath} へのアクセスが拒否されました。”)
except Exception as e:
print(f”エラー: {filepath} の処理中に予期せぬエラーが発生しました: {e}”)
“`
9. 高度なトピック/代替手段:より複雑な要件のために
glob
で対応できない、あるいはより低レベルな制御が必要な場合のための代替手段や関連モジュールを紹介します。
9.1. fnmatch
モジュール:パターンマッチングロジックのみ
fnmatch
モジュールは、ファイル名パターンマッチングのロジック自体を提供します。glob
とは異なり、ファイルシステムを走査する機能はなく、単に文字列が特定のシェルスタイルのワイルドカードパターンに一致するかどうかを判定するために使用されます。
fnmatch.fnmatch(name, pat)
: 文字列name
がパターンpat
に一致するかどうかをテストします。fnmatch.filter(names, pat)
: 文字列のリストnames
から、パターンpat
に一致する名前だけを抽出します。
使用例:
特定のリスト内のファイル名だけをフィルタリングしたい場合。
“`python
import fnmatch
file_list = [
‘report_2023.txt’,
‘report_2022.csv’,
‘image.jpg’,
‘document.pdf’,
‘data_log.txt’
]
.txt ファイルをフィルタリング
txt_files = fnmatch.filter(file_list, ‘*.txt’)
print(f”fnmatchでフィルタリングされた.txtファイル: {txt_files}”)
‘report’ で始まるファイルをフィルタリング
reports = fnmatch.filter(file_list, ‘report*’)
print(f”fnmatchでフィルタリングされたレポートファイル: {reports}”)
個別のマッチングテスト
print(f”‘report_2023.txt’ は ‘report_????.txt’ にマッチするか? {fnmatch.fnmatch(‘report_2023.txt’, ‘report_????.txt’)}”)
print(f”‘data_log.txt’ は ‘.csv’ にマッチするか? {fnmatch.fnmatch(‘data_log.txt’, ‘.csv’)}”)
``
fnmatch`は、すでにファイルのリストを持っている場合や、ファイルシステムにアクセスせずにパターンマッチングだけを行いたい場合に役立ちます。
9.2. re
モジュール:複雑な正規表現によるマッチング
前述の通り、re
(正規表現)モジュールは、glob
よりもはるかに強力で柔軟なパターンマッチングを可能にします。ファイル名だけでなく、ファイルの内容を検索したり、特定の情報を抽出したりする場合に不可欠です。
“`python
import re
import os
仮のファイルを作成
with open(“sales_report_2023_10_26.csv”, “w”) as f: f.write(“test”)
with open(“inventory_2023_11_15.log”, “w”) as f: f.write(“test”)
ファイル名から日付を抽出する例
globでファイルパスを取得後、reでさらにフィルタリングと情報抽出
file_paths = glob.glob(‘.csv’) + glob.glob(‘.log’) # 複数の拡張子をglobで収集
date_pattern = re.compile(r'(\d{4})(\d{2})(\d{2})’)
for path in file_paths:
match = date_pattern.search(os.path.basename(path))
if match:
year, month, day = match.groups()
print(f”ファイル: {path}, 日付: {year}-{month}-{day}”)
“`
9.3. os.walk()
:低レベルなディレクトリツリー走査
os.walk()
は、ディレクトリツリー全体を巡回し、各ディレクトリでそのパス、サブディレクトリのリスト、ファイルのリストを提供します。glob
の**
(recursive=True)がシンプルに目的のファイルを列挙するのに対し、os.walk()
はより細かな制御が可能です。例えば、特定のサブディレクトリだけをスキップしたり、検索中に特定の条件を満たすディレクトリを削除したりするなどの複雑なロジックを実装できます。
“`python
import os
print(“\n— os.walk() を使ったディレクトリツリー走査 —“)
for root, dirs, files in os.walk(‘test_data’):
print(f”現在のディレクトリ: {root}”)
print(f” サブディレクトリ: {dirs}”)
print(f” ファイル: {files}”)
# 特定のディレクトリをスキップする例 (dirsリストをin-placeで変更)
# if 'images' in dirs:
# dirs.remove('images') # 'images' ディレクトリ以下は探索しない
“`
glob
とos.walk
は互いに排他的なものではなく、相互に補完し合う関係にあります。シンプルなファイルパス列挙ならglob
、より複雑なツリー構造のナビゲーションとカスタマイズが必要ならos.walk
、と使い分けるのが良いでしょう。
10. トラブルシューティング:よくある問題とその解決策
glob
を使っている際によく遭遇する問題と、その解決策をいくつか紹介します。
-
「何もマッチしない」問題:
- パターンが間違っている: 最も一般的な原因です。ワイルドカードの指定ミス(例:
*.txt
とすべきところを*.tx
と書いてしまった)。特に?
や[]
の使用は注意が必要です。 - パスが間違っている:
glob
のパターンは、実行しているPythonスクリプトのカレントディレクトリを基準としています。意図したディレクトリで実行されているか確認しましょう。絶対パスを使用するか、os.chdir()
でカレントディレクトリを変更することで、この問題を回避できます。 recursive=True
が不足している: サブディレクトリ内のファイルを検索しているのに、recursive=True
を忘れていませんか?**
を使ってもrecursive=True
がなければ機能しません。- 隠しファイルがターゲット: 隠しファイル(
.
で始まるファイル)を検索している場合、*
ではマッチしません。.*
や**/*
だけでなく**/.*
も考慮しましょう。 - 大文字・小文字の区別: OSが大文字・小文字を区別する環境で、パターンと実際のファイル名の大文字・小文字が一致しない場合。
- パターンが間違っている: 最も一般的な原因です。ワイルドカードの指定ミス(例:
-
パフォーマンス問題:
**
の使用範囲が広すぎる: ドライブのルートディレクトリ(例:/
やC:/
)から**
を使って検索すると、非常に時間がかかります。可能な限り、検索の開始パスを限定しましょう。- ネットワークドライブでの検索: ネットワークドライブ上のファイルはアクセス速度が遅いため、検索に時間がかかります。
- メモリ使用量が多い: 大量のファイルがマッチする場合、
glob.glob()
は大量のメモリを消費します。glob.iglob()
を使用することで、この問題を解決できます。
-
OS間の互換性問題:
- パス区切り文字: Windows (
\
) とUNIX系 (/
) のパス区切り文字の違いは、Pythonではほとんど吸収されますが、手動でパスを結合する際にos.path.join
やpathlib
を使わないと問題になることがあります。パターン文字列では/
を使用する習慣をつけましょう。 - 隠しファイル、大文字・小文字: 前述の通り、これらの挙動はOSによって異なります。クロスプラットフォーム対応が必要な場合は、コードでこれらの違いを考慮に入れる必要があります。
- パス区切り文字: Windows (
まとめ:glob
をファイル検索の強力な味方に
Pythonのglob
モジュールは、そのシンプルさと強力さで、ファイルシステム内のパスを検索する上で非常に有用なツールです。UNIXシェルでお馴染みのワイルドカード構文をPythonで直接利用できるため、直感的に学習しやすく、日々の開発やデータ処理のタスクを効率化します。
本記事で学んだ重要なポイント:
glob
は、ワイルドカード(*
,?
,[]
,**
)を用いてファイルパスをパターンマッチングします。*
は0個以上の任意の文字、?
は1個の任意の文字、[]
は文字の集合のいずれか1文字にマッチします。- Python 3.5以降では、
**
とrecursive=True
を組み合わせることで、再帰的なディレクトリ検索が可能です。 glob.glob()
はマッチしたパスのリストを返し、glob.iglob()
はメモリ効率の良いイテレータを返します。大規模な検索ではiglob()
が推奨されます。pathlib
モジュールと連携することで、よりモダンでオブジェクト指向的なパス操作とglob
検索を統合できます。- 隠しファイル、大文字・小文字の区別、シンボリックリンク、パフォーマンスといった注意点を理解し、適切なベストプラクティスを適用することが重要です。
- より複雑なパターンマッチングやディレクトリツリーの低レベルな走査が必要な場合は、
fnmatch
、re
、os.walk
などの代替モジュールを検討しましょう。
ファイル検索は、プログラミングにおける基本的なタスクの一つです。glob
モジュールをマスターすることで、あなたはデータ処理、自動化スクリプト、システム管理など、幅広い分野でより効率的かつ堅牢なコードを書くことができるようになるでしょう。ぜひ、今日からあなたのプロジェクトでglob
を積極的に活用してみてください。