Python globでファイル検索!ワイルドカードの基本と使い方を徹底解説

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でファイルやディレクトリを検索する方法はいくつかあります。

  1. 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)
    “`

  2. os.walk():
    ディレクトリツリーを再帰的に走査するための強力なツールです。特定のディレクトリとそのサブディレクトリ内のすべてのファイルとフォルダを効率的に処理できます。しかし、os.walk()自体はパターンマッチング機能を持たないため、結果をフィルタリングするためにはやはり追加のロジック(fnmatchreなど)が必要です。

    “`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)
    “`

  3. 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*/*.txtdirで始まるディレクトリ直下の.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)は大文字・小文字を区別しません。*.txtfile.txtFILE.TXTもマッチします。
  • Linux/macOS: 通常、ファイルシステム(Ext4, APFSなど)は大文字・小文字を区別します。*.txtfile.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から日付部分だけを取り出す)。
    • 部分的な文字列の一致ではなく、厳密な構文チェック。

使い分けの指針:
* ファイルパスの列挙が主な目的で、パターンがシンプルなら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()

pathlibPathオブジェクトは、独自の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.pathglobを個別に使うよりも、pathlibを積極的に利用することが推奨されます。

8. ベストプラクティス:効率的で堅牢なglob利用法

globを最大限に活用し、コードを堅牢にするためのベストプラクティスをいくつか紹介します。

  1. 結果は常にソートする:
    glob.glob()が返すリストの順序は保証されません。結果の再現性や予測可能性を確保するため、sorted()関数で明示的にソートしましょう。

    python
    import glob
    files = sorted(glob.glob('data/*.csv'))

  2. recursive=Trueの必要性を検討する:
    安易にrecursive=Trueを使用すると、不要な深さまで検索してしまい、パフォーマンスの低下や予期せぬ結果を招く可能性があります。必要な場合にのみ使用し、検索パスをできるだけ具体的に指定して範囲を絞りましょう。

  3. 大規模な検索ではiglobを優先する:
    数千個以上のファイルがマッチする可能性がある場合は、glob.iglob()を使用してメモリ使用量を最適化し、ジェネレータの利点を活かしましょう。

  4. OS間の互換性を考慮する:

    • パス区切り文字は、パターン文字列では/で統一することをお勧めします。Pythonが適切に変換します。
    • 隠しファイルや大文字・小文字の扱いはOSによって異なるため、クロスプラットフォーム対応が必要な場合は、これらの違いをコードで吸収するロジック(例: filename.lower())を検討しましょう。
    • パスの結合にはos.path.joinまたはpathlib.Pathを使用し、OS依存のパス区切り文字の問題を回避しましょう。

    “`python
    import os
    from pathlib import Path

    os.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())

    “`

  5. エラーハンドリングを考慮する:
    glob関数自体はファイルが見つからなくてもエラーになりませんが、検索したファイルパスを使って何か処理を行う際には、FileNotFoundErrorPermissionErrorなどが発生する可能性があります。適切なtry-exceptブロックでこれらのエラーを捕捉し、堅牢なコードを書きましょう。

    “`python
    import glob
    import os

    for 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' ディレクトリ以下は探索しない

“`

globos.walkは互いに排他的なものではなく、相互に補完し合う関係にあります。シンプルなファイルパス列挙ならglob、より複雑なツリー構造のナビゲーションとカスタマイズが必要ならos.walk、と使い分けるのが良いでしょう。

10. トラブルシューティング:よくある問題とその解決策

globを使っている際によく遭遇する問題と、その解決策をいくつか紹介します。

  1. 「何もマッチしない」問題:

    • パターンが間違っている: 最も一般的な原因です。ワイルドカードの指定ミス(例: *.txtとすべきところを*.txと書いてしまった)。特に?[]の使用は注意が必要です。
    • パスが間違っている: globのパターンは、実行しているPythonスクリプトのカレントディレクトリを基準としています。意図したディレクトリで実行されているか確認しましょう。絶対パスを使用するか、os.chdir()でカレントディレクトリを変更することで、この問題を回避できます。
    • recursive=Trueが不足している: サブディレクトリ内のファイルを検索しているのに、recursive=Trueを忘れていませんか?**を使ってもrecursive=Trueがなければ機能しません。
    • 隠しファイルがターゲット: 隠しファイル(.で始まるファイル)を検索している場合、*ではマッチしません。.***/*だけでなく**/.*も考慮しましょう。
    • 大文字・小文字の区別: OSが大文字・小文字を区別する環境で、パターンと実際のファイル名の大文字・小文字が一致しない場合。
  2. パフォーマンス問題:

    • **の使用範囲が広すぎる: ドライブのルートディレクトリ(例: /C:/)から**を使って検索すると、非常に時間がかかります。可能な限り、検索の開始パスを限定しましょう。
    • ネットワークドライブでの検索: ネットワークドライブ上のファイルはアクセス速度が遅いため、検索に時間がかかります。
    • メモリ使用量が多い: 大量のファイルがマッチする場合、glob.glob()は大量のメモリを消費します。glob.iglob()を使用することで、この問題を解決できます。
  3. OS間の互換性問題:

    • パス区切り文字: Windows (\) とUNIX系 (/) のパス区切り文字の違いは、Pythonではほとんど吸収されますが、手動でパスを結合する際にos.path.joinpathlibを使わないと問題になることがあります。パターン文字列では/を使用する習慣をつけましょう。
    • 隠しファイル、大文字・小文字: 前述の通り、これらの挙動はOSによって異なります。クロスプラットフォーム対応が必要な場合は、コードでこれらの違いを考慮に入れる必要があります。

まとめ:globをファイル検索の強力な味方に

Pythonのglobモジュールは、そのシンプルさと強力さで、ファイルシステム内のパスを検索する上で非常に有用なツールです。UNIXシェルでお馴染みのワイルドカード構文をPythonで直接利用できるため、直感的に学習しやすく、日々の開発やデータ処理のタスクを効率化します。

本記事で学んだ重要なポイント:

  • globは、ワイルドカード(*, ?, [], **)を用いてファイルパスをパターンマッチングします。
  • *は0個以上の任意の文字、?は1個の任意の文字、[]は文字の集合のいずれか1文字にマッチします。
  • Python 3.5以降では、**recursive=Trueを組み合わせることで、再帰的なディレクトリ検索が可能です。
  • glob.glob()はマッチしたパスのリストを返し、glob.iglob()はメモリ効率の良いイテレータを返します。大規模な検索ではiglob()が推奨されます。
  • pathlibモジュールと連携することで、よりモダンでオブジェクト指向的なパス操作とglob検索を統合できます。
  • 隠しファイル、大文字・小文字の区別、シンボリックリンク、パフォーマンスといった注意点を理解し、適切なベストプラクティスを適用することが重要です。
  • より複雑なパターンマッチングやディレクトリツリーの低レベルな走査が必要な場合は、fnmatchreos.walkなどの代替モジュールを検討しましょう。

ファイル検索は、プログラミングにおける基本的なタスクの一つです。globモジュールをマスターすることで、あなたはデータ処理、自動化スクリプト、システム管理など、幅広い分野でより効率的かつ堅牢なコードを書くことができるようになるでしょう。ぜひ、今日からあなたのプロジェクトでglobを積極的に活用してみてください。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール