[解決!Python]globモジュールを使って特定のパターンにマッチするパス名を取得するには解決!Python

globモジュールのglob関数を使うと、特定のパターンを指定して、それにマッチしたファイルやディレクトリのパス名を取得できる。その基本的な使い方を紹介する。

» 2023年10月03日 05時00分 公開
[かわさきしんじDeep Insider編集部]
「解決!Python」のインデックス

連載目次

# サンプルファイルの用意
from pathlib import Path

files = ['1.txt', '2.txt', 'foo.csv', 'bar.csv', 'sample', '.foorc']
for name in files:
    Path(name).touch()

files = ['data01.csv', 'data02.csv', 'txtdata03.txt']
Path('data').mkdir(exist_ok=True)
for name in files:
    Path('data', name).touch()

hidden_files = ['.barrc', '.bazrc']
Path('.hidden').mkdir(exist_ok=True)
for name in hidden_files:
    Path('.hidden', name).touch()

import glob

# 全てのパス名を取得(隠しファイルを除く)
result = glob.glob('*')
print(result)
# 出力結果:
#['foo.csv', 'data', 'bar.csv', '2.txt', 'sample.py', 'sample', '1.txt']

# 拡張子を持つ全てのパス名を取得
result = glob.glob('*.*')
print(result)
# 出力結果:
#['foo.csv', 'bar.csv', '2.txt', 'sample.py', '1.txt']

# 拡張子が「.csv」のパス名を取得
result = glob.glob('*.csv')
print(result)  # ['foo.csv', 'bar.csv']

# 「1文字のベース名.拡張子」のパス名を取得
result = glob.glob('?.*')
print(result)  # ['2.txt', '1.txt']

# 「f」か「1」で始まる3文字のベース名と拡張子のパス名を取得
result = glob.glob('[f1]??.*')
print(result)  # ['foo.csv']

# 「1」で始まらない拡張子が「txt」のパス名を取得
result = glob.glob('[!1].txt')
print(result)  # ['2.txt']

# 隠しファイル(名前がドット「.」で始まるファイル/ディレクトリ)のパス名を取得
result = glob.glob('.*')
print(result)  # ['.hidden', '.foorc']

# 「data」ディレクトリを検索対象とする:Python 3.10以降
result = glob.glob('*.*', root_dir='data')
print(result)  # ['txtdata03.txt', 'data01.csv', 'data02.csv']

result = glob.glob('data/*')
print(result)
# 出力結果:
#['data/txtdata03.txt', 'data/data01.csv', 'data/data02.csv']
# 出力結果:(Windows)
#['data\\data01.csv', 'data\\data02.csv', 'data\\txtdata03.txt']

# 再帰的に全てのパス名を取得
result = glob.glob('**', recursive=True)
print(result)
# 出力結果:
#['foo.csv', 'data', 'data/txtdata03.txt', 'data/data01.csv',
# 'data/data02.csv', 'bar.csv', '2.txt', 'sample.py', 'sample', '1.txt']
# 出力結果:(Windows)
#['1.txt', '2.txt', 'bar.csv', 'data', 'data\\data01.csv', 'data\\data02.csv',
# 'data\\txtdata03.txt', 'foo.csv', 'sample', 'sample.py']

# 再帰的に全てのパス名を取得(隠しファイルを含む):Python 3.11以降
result = glob.glob('**', recursive=True, include_hidden=True)
print(result)
# 出力結果:
#['.hidden', '.hidden/.barrc', '.hidden/.bazrc', 'foo.csv', 'data',
# 'data/txtdata03.txt', 'data/data01.csv', 'data/data02.csv', 'bar.csv',
# '2.txt', 'sample.py', 'sample', '1.txt', '.foorc']
# 出力結果:(Windows)
#['.foorc', '.hidden', '.hidden\\.barrc', '.hidden\\.bazrc', '1.txt', '2.txt',
# 'bar.csv', 'data', 'data\\data01.csv', 'data\\data02.csv',
# 'data\\txtdata03.txt', 'foo.csv', 'sample', 'sample.py']

# 特殊文字をエスケープする
Path(r'?.txt').touch()
result = glob.glob('?.txt')
print(result)  # ['?.txt', '2.txt', '1.txt']

target = glob.escape('?.txt')
print(target)  # [?].txt

result = glob.glob(target)
print(result)  # ['?.txt']


特殊文字 意味
* 0文字以上の任意の文字にマッチする
? 任意の1文字にマッチする
[abc] []内に記述された文字(左の例なら'a'、'b'、'c')のいずれかにマッチする。マイナス記号「-」で範囲を指定可能(例:[a-z]はアルファベットの小文字のいずれかにマッチする)
[!abc] []内に記述された以外の文字(左の例なら'a'、'b'、'c'以外)のいずれかにマッチする。マイナス記号「-」で範囲を指定可能(例:[!a-z]はアルファベットの小文字以外にマッチする)
glob.glob関数で使用できる特殊文字

globモジュール

 Pythonに標準添付のglobモジュールはカレントディレクトリまたは指定したディレクトリに存在するファイル/ディレクトリの中で、特定のパターンにマッチするもののパス名を取得するのに使える。特定のパターンとは通常の文字と特殊文字と呼ばれる?/*/[]のパターンを組み合わせて表現される。

 このとき、?は任意の1文字に、*は0文字以上の任意の文字列に、[]はその中に記述したいずれか1文字にマッチする。また、[]では「a〜zまでのどれか」「a〜zには含まれないどれか」のような指定も可能である。前者であれば[a-z]と、後者であれば[!a-z]とする。

 globモジュールが提供するのは主に以下の3つの関数だ。

  • glob.glob関数:指定したパターンにマッチするパス名を要素とするリストを返す
  • glob.iglob関数:指定したパターンにマッチするパス名を列挙するイテレーターを返す
  • glob.escape関数:特殊文字をエスケープする。特殊文字を含むパターンを作成するのに使用する

 glob.glob関数とglob.iglob関数はリストを返すか、イテレーターを返すかが異なる。そのため、本記事ではglob.glob関数のみを例として取り上げる。glob.escape関数は上記表に示した特殊文字をそのままの意味でパターンの中で使えるようにエスケープを行う(最後に例を示す)。

 なお、ここでは以下のコードで幾つかのサンプルファイル/サンプルディレクトリを作成しておく。

from pathlib import Path

files = ['1.txt', '2.txt', 'foo.csv', 'bar.csv', 'sample', '.foorc']
for name in files:
    Path(name).touch()

files = ['data01.csv', 'data02.csv', 'txtdata03.txt']
Path('data').mkdir(exist_ok=True)
for name in files:
    Path('data', name).touch()

hidden_files = ['.barrc', '.bazrc']
Path('.hidden').mkdir(exist_ok=True)
for name in hidden_files:
    Path('.hidden', name).touch()


 カレントディレクトリには以下のファイルとディレクトリを作成する。

ファイル/ディレクトリ 作成するもの
ファイル 1.txt/2.txt/foo.csv/bar.csv/sample/.foorc(.foorcはいわゆる隠しファイル)
ディレクトリ data(3つのファイルを含む)/.hidden(隠しディレクトリ)
カレントディレクトリに作成するファイル/ディレクトリ

 作成するファイルの中で.foorcファイルはいわゆる隠しファイル(Unix系統のOSではlsコマンドにオプションを指定しないと画面に出力されないファイル)となっている。これは.hiddenディレクトリも同様だ。また、dataディレクトリには3つのファイルが、.hiddenディレクトリには2つのファイルが含まれている。

glob.glob関数によるパス名の展開

 glob.glob関数の基本構文を以下に示す。

glob.glob(pathname, *, rood_dir=None, recursive=False, include_hidden=False)


 pathnameには取得したパス名にマッチするパターンを指定する。root_dirにはパス名の検索対象となるディレクトリを指定する(Python 3.10以降)。指定しなければ、現在プログラムを実行しているディレクトリが対象となる。recursiveにTrueを指定すると、再帰的にパス名の取得が行われる。このとき、pathnameに'**'を含めると、'**'は全てのファイルやディレクトリにマッチするようになる。さらにinclude_hiddenにTrueを指定すると'**'は隠しファイル/隠しディレクトリにもマッチするようになる(Python 3.11以降)。

 簡単な例を以下に示す。

import glob

result = glob.glob('*')
print(result)
# 出力結果:
#['foo.csv', 'data', 'bar.csv', '2.txt', 'sample.py', 'sample', '1.txt']


 この例ではpathnameに'*'を指定している。'*'は0文字以上の任意の文字列にマッチするので、この場合はプログラムを現在実行しているディレクトリに存在するファイル/ディレクトリ全てがマッチして、それらのパス名が取得される(sample.pyファイルは上記のコードを含んだスクリプトファイル)。

 このとき、どんな順序でパス名がリストに格納されるかはファイルシステムによる。何らかの順序を期待することはできないので、特定の順序にしたいのであれば、sortメソッドやsorted関数を使うようにしよう。

 次の例は何らかの拡張子を持つものを取得する。

result = glob.glob('*.*')
print(result)
# 出力結果:
#['foo.csv', 'bar.csv', '2.txt', 'sample.py', '1.txt']


 ここで指定しているパターン「*.*」は「0文字以上の任意の文字列+ドット+0文字以上の任意の文字列」を意味するので、カレントディレクトリにある拡張子付きのファイルがここでは取得されている(隠しディレクトリと隠しファイルが取得されていないが、これらを含めて取得するには上記呼び出しでinclude_hidden=Trueを指定するとよい)。

 次は拡張子が「.csv」であるファイルのパス名を取得する例だ。

result = glob.glob('*.csv')
print(result)  # ['foo.csv', 'bar.csv']


 これについては説明は不要だろう。次にファイル名のベース名(拡張子以外の部分)が1文字のファイルを取得する例だ。

result = glob.glob('?.*')
print(result)  # ['2.txt', '1.txt']


 '?'というパターンは任意の1文字にマッチする。そして、ここでは'?.*'というパターンを指定しているので、これは「任意の1文字+ドット+0文字以上の任意の文字列」を意味する。

 次は角かっこ「[]」を使用する例だ。

result = glob.glob('[f1]??.*')
print(result)  # ['foo.csv']


 この例では'[f1]??.*'というパターンを指定している。パターン先頭の角かっこの中には'f'と'1'があるので、これはパス名が'f'か'1'で始まることを意味する。その後には'??'とあるが、これは任意の2文字を意味する。拡張子の部分はこれまでに見てきた通りだ。よって、これは「'f'か'1'で始まり、ベース名が合計で3文字で拡張子を持つパス名」を意味する。その結果'foo.csv'が得られた。

 次は角かっこに!を指定する例だ。

result = glob.glob('[!1].txt')
print(result)  # ['2.txt']


 この例ではパターンに'[!1].txt'を指定している。角かっこ内の先頭に'!'があるので、これはその後に記述された文字を含まないことを意味する。よって、これは「ベース名が'1'で始まらない、拡張子が'.txt'にマッチするパス名」となる。このため、'2.txt'だけが得られる。

 既に述べたが、隠しファイルや隠しディレクトリを取得するには、パターンに'.*'などを指定するとよい。

result = glob.glob('.*')
print(result)  # ['.hidden', '.foorc']


 ここではこのパターンを指定しているので、隠しディレクトリである'.hidden'と隠しファイルである'.foorc'が得られている。

 root_dirパラメーターには検索対象のディレクトリを指定できる(Python 3.10以降)。以下に例を示す。

result = glob.glob('*.*', root_dir='data')
print(result)  # ['txtdata03.txt', 'data01.csv', 'data02.csv']


 最初に準備したときに、カレントディレクトリにはdataディレクトリがあり、その下には3つのファイルを作成した。ここでは「root_dir='data'」として、そのディレクトリを指定して、パターンを'*.*'としたので3つのファイルが得られている。

 なお、パターンの中に以下のように検索対象のディレクトリを含めることも可能だ。

result = glob.glob('data/*')
print(result)
# 出力結果:
#['data/txtdata03.txt', 'data/data01.csv', 'data/data02.csv']
# 出力結果:(Windows)
#['data\\data01.csv', 'data\\data02.csv', 'data\\txtdata03.txt']


 このときには、検索対象のディレクトリを含むパス名が得られるのが、root_dirパラメーターを指定するときとの差となる(root_dirパラメーターを指定するのは、そのディレクトリにカレントディレクトリを移動するのと同様な効果があるということだ)。また、ディレクトリの区切り文字がWindowsでは異なる点は(お分かりだろうが)注意されたい。

 recursiveパラメーターにTrueを指定すると、パス名の再帰的な取得が行われる。以下に例を示す。

result = glob.glob('**', recursive=True)
print(result)
# 出力結果:
#['foo.csv', 'data', 'data/txtdata03.txt', 'data/data01.csv',
# 'data/data02.csv', 'bar.csv', '2.txt', 'sample.py', 'sample', '1.txt']
# 出力結果:(Windows)
#['1.txt', '2.txt', 'bar.csv', 'data', 'data\\data01.csv', 'data\\data02.csv',
# 'data\\txtdata03.txt', 'foo.csv', 'sample', 'sample.py']


 ここでは'**'をパターンとしているが、上述した通り、これは全てのファイルやディレクトリなどにマッチする。ここではカレントディレクトリにあるディレクトリとファイル、サブディレクトリにあるファイルが取得されている。ただし、隠しファイルと隠しディレクトリは含まれていない。それらを含めるにはinclude_hiddenパラメーターにTrueを指定する。ただし、このパラメーターを指定できるのはPython 3.11以降のみとなる。

result = glob.glob('**', recursive=True, include_hidden=True)
print(result)
# 出力結果:
#['.hidden', '.hidden/.barrc', '.hidden/.bazrc', 'foo.csv', 'data',
# 'data/txtdata03.txt', 'data/data01.csv', 'data/data02.csv', 'bar.csv',
# '2.txt', 'sample.py', 'sample', '1.txt', '.foorc']
# 出力結果:(Windows)
#['.foorc', '.hidden', '.hidden\\.barrc', '.hidden\\.bazrc', '1.txt', '2.txt',
# 'bar.csv', 'data', 'data\\data01.csv', 'data\\data02.csv',
# 'data\\txtdata03.txt', 'foo.csv', 'sample', 'sample.py']


 今度は隠しディレクトリにある隠しファイルやカレントディレクトリにある隠しファイルも取得されたことを確認しよう。

 最後にglob.escape関数の使い方を簡単に見てみよう。

 例えば、次のようにして「?.txt」というファイルを作成してみよう。

Path(r'?.txt').touch()


 カレントディレクトリにはベース名が1文字のファイルとしては他にも1.txtファイルと2.txtファイルがある。このとき、?.txtファイルだけを得たいとする。そこで、パターンを'?.txt'としてglob.glob関数を呼び出すと、次のように3つのファイルが得られてしまう。

result = glob.glob('?.txt')
print(result)  # ['?.txt', '2.txt', '1.txt']


 このようなときに、glob.escape関数を使用する。これにパターンを渡すと、特殊文字(?/*/[])をエスケープしたパターンが返される。試しに'?.txt'を渡すと次のようになる。

target = glob.escape('?.txt')
print(target)  # [?].txt


 エスケープといってもバックスラッシュによるものとは異なり、ここでは角かっこにエスケープが必要な特殊文字が埋め込まれるだけである(角かっこ内の文字は文字本来の意味を取るようになるということだ)。エスケープされたパターンを使ってglob.glob関数を呼び出すと、希望通りに?.txtファイルのみが得られる。

result = glob.glob(target)
print(result)  # ['?.txt']


「解決!Python」のインデックス

解決!Python

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。