note.nkmk.me

Pythonで条件を満たすパスの一覧を再帰的に取得するglobの使い方

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

Pythonのglobモジュールを使うと、ワイルドカード*などの特殊文字を使って条件を満たすファイル名・ディレクトリ(フォルダ)名などのパスの一覧をリストやイテレータで取得できる。

globモジュールは標準ライブラリに含まれているのでインストールは不要(importは必要)。

ここでは以下の内容について説明する。

  • glob()の基本的な使い方
  • glob()で使用できる特殊文字
    • 長さ0文字以上の任意の文字列: *
    • 任意の一文字: ?
    • 特定の一文字: []
    • 特殊文字のエスケープ
  • 再帰的に取得: 引数recursive
  • ファイル名のみ取得
  • ディレクトリ名のみ取得
  • 正規表現で条件指定
  • イテレータで一覧を取得: iglob()

Python3.4以降ではパスをオブジェクトとして操作できるpathlibモジュールを使ってglob処理を行うことも可能。条件で抽出したあとで削除などの処理を行う場合はpathlibのほうが便利。

以下のようなファイル、ディレクトリ構成を例とする。

temp/
├── 1.txt
├── 12.text
├── 123.txt
├── [x].txt
├── aaa.text
└── dir
    ├── 987.text
    ├── bbb.txt
    ├── sub_dir1
    │   ├── 98.txt
    │   └── ccc.text
    └── sub_dir2
        └── ddd.text
スポンサーリンク

glob()の基本的な使い方

glob()の第一引数にパスの文字列を指定する。ワイルドカード*などの特殊文字が使用可能。

条件を満たすパスがリスト(list型)で取得できる。

import glob
import re
import os

l = glob.glob('temp/*.txt')

print(l)
# ['temp/[x].txt', 'temp/1.txt', 'temp/123.txt']

print(type(l))
# <class 'list'>

glob()で使用できる特殊文字

glob()では以下の特殊文字が使用できる。正規表現を使う方法は後述。

長さ0文字以上の任意の文字列: *

*はいわゆるワイルドカード文字。長さ0文字以上の任意の文字列にマッチする。

print(glob.glob('temp/*'))
# ['temp/[x].txt', 'temp/aaa.text', 'temp/dir', 'temp/1.txt', 'temp/12.text', 'temp/123.txt']

print(glob.glob('temp/*.txt'))
# ['temp/[x].txt', 'temp/1.txt', 'temp/123.txt']

print(glob.glob('temp/dir/*/*.text'))
# ['temp/dir/sub_dir1/ccc.text', 'temp/dir/sub_dir2/ddd.text']

任意の一文字: ?

?は任意の一文字にマッチする。

例えば拡張子を除くファイル名が3文字のパスを抽出したい場合は???.*とする。

print(glob.glob('temp/???.*'))
# ['temp/[x].txt', 'temp/aaa.text', 'temp/123.txt']

特定の一文字: []

[]で囲むと括弧の中の文字列の中の一文字にマッチする。

例えば[0-9]は0から9までの数字のどれか一文字、[a-z]はaからzまでのアルファベットのどれか一文字にマッチする。

print(glob.glob('temp/[0-9].*'))
# ['temp/1.txt']

print(glob.glob('temp/[0-9][0-9].*'))
# ['temp/12.text']

print(glob.glob('temp/[a-z][a-z][a-z].*'))
# ['temp/aaa.text']

特殊文字のエスケープ

*, ?, [を含む文字列を対象としたい場合はそれらを[]で囲む。

print(glob.glob('temp/[[]*'))
# ['temp/[x].txt']

再帰的に取得: 引数recursive

Python3.5から**を使った再帰的な処理がサポートされた。

引数recursive=Trueとして**を使うと、あらゆるファイルや0個以上のディレクトリおよびサブディレクトリにマッチする。

例えば*を使って任意のディレクトリ名にマッチさせると同一階層のファイルしか抽出できないが、**を使うとあらゆる中間ディレクトリに対応してマッチさせることができる。

print(glob.glob('temp/dir/*/*.text'))
# ['temp/dir/sub_dir1/ccc.text', 'temp/dir/sub_dir2/ddd.text']

print(glob.glob('temp/**/*.text', recursive=True))
# ['temp/aaa.text', 'temp/12.text', 'temp/dir/987.text', 'temp/dir/sub_dir1/ccc.text', 'temp/dir/sub_dir2/ddd.text']

特定のディレクトリに含まれるすべてのファイル、ディレクトリの一覧を取得したい場合は<ディレクトリ名>/**とすればOK。

print(glob.glob('temp/**', recursive=True))
# ['temp/', 'temp/[x].txt', 'temp/aaa.text', 'temp/dir', 'temp/dir/sub_dir1', 'temp/dir/sub_dir1/98.txt', 'temp/dir/sub_dir1/ccc.text', 'temp/dir/987.text', 'temp/dir/bbb.txt', 'temp/dir/sub_dir2', 'temp/dir/sub_dir2/ddd.text', 'temp/1.txt', 'temp/12.text', 'temp/123.txt']

ファイル名のみ、ディレクトリ名のみを抽出する方法は次に説明する。

ファイル名のみ取得

ファイル名のみ取得したい場合は、os.path.isfile()でファイルかどうかを判定する。

リスト内包表記の条件分岐でos.path.isfile()を使うとファイル名のみのリストが取得できる。

print([p for p in glob.glob('temp/**', recursive=True)
       if os.path.isfile(p)])
# ['temp/[x].txt', 'temp/aaa.text', 'temp/dir/sub_dir1/98.txt', 'temp/dir/sub_dir1/ccc.text', 'temp/dir/987.text', 'temp/dir/bbb.txt', 'temp/dir/sub_dir2/ddd.text', 'temp/1.txt', 'temp/12.text', 'temp/123.txt']

親ディレクトリの情報が必要ない場合はos.path.basename()で末尾のファイル名のみを抽出できる。

print([os.path.basename(p) for p in glob.glob('temp/**', recursive=True)
       if os.path.isfile(p)])
# ['[x].txt', 'aaa.text', '98.txt', 'ccc.text', '987.text', 'bbb.txt', 'ddd.text', '1.txt', '12.text', '123.txt']

ディレクトリ名のみ取得

ディレクトリ名のみ取得したい場合は、os.path.isdir()を使ってもよいが、**の末尾にディレクトリの区切り文字をつけるほうがシンプルで簡単。

print(glob.glob('temp/**/', recursive=True))
# ['temp/', 'temp/dir/', 'temp/dir/sub_dir1/', 'temp/dir/sub_dir2/']

例はMacの場合。Windowsでは区切り文字が'\\'となる。各OSでの区切り文字はos.sepで取得できるので、OSに依存しないコードにしたい場合は以下のように書ける。

print(os.path.join('temp', '**' + os.sep))
# temp/**/

print(glob.glob(os.path.join('temp', '**' + os.sep), recursive=True))
# ['temp/', 'temp/dir/', 'temp/dir/sub_dir1/', 'temp/dir/sub_dir2/']

親ディレクトリの情報が必要ない場合は文字列メソッドのrstrip()で末尾の区切り文字を削除した上でos.path.basename()を使う。

print([os.path.basename(p.rstrip(os.sep)) for p
       in glob.glob(os.path.join('temp', '**' + os.sep), recursive=True)])
# ['temp', 'dir', 'sub_dir1', 'sub_dir2']

正規表現で条件指定

*?などである程度の条件指定は可能だが、複雑な条件で抽出したい場合は正規表現を使うと便利。

正規表現はreモジュールを使う。

glob()**と引数recursive=Trueを使ってすべてのファイル、ディレクトリを再帰的にリストアップしてからre.search()などで正規表現を使った判定を行う。

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

print([p for p in glob.glob('temp/**', recursive=True)
       if re.search('\d+\.txt', p)])
# ['temp/dir/sub_dir1/98.txt', 'temp/1.txt', 'temp/123.txt']

print([p for p in glob.glob('temp/**', recursive=True)
       if re.search('/\D{3}\.(txt|text)', p)])
# ['temp/[x].txt', 'temp/aaa.text', 'temp/dir/sub_dir1/ccc.text', 'temp/dir/bbb.txt', 'temp/dir/sub_dir2/ddd.text']

\dは数字、+は1回以上の繰り返し。\Dは数字以外の文字、{n}はn回の繰り返し。また、(a|b)はa, bのいずれかとなる。globだけでは実現できない複雑な条件が指定できる。

なお、ファイル・ディレクトリ数が多い場合にglob()**を使うと時間がかかる可能性があるので、他の特殊文字で条件を絞れる場合はそちらを使った上で正規表現でさらに絞るほうがいい。

イテレータで一覧を取得: iglob()

これまでの例のようにglob()はパスのリストを生成する。

ファイルやディレクトリが少なければ特に気にする必要はないが、抽出したパスをfor文などで処理する場合は、リストではなくイテレータを使ったほうがメモリ使用量が抑えられる。

iglob()を使うとイテレータが返される。引数はglob()と同じでrecursiveも使える。

print(type(glob.iglob('temp/*.txt')))
# <class 'generator'>

for p in glob.iglob('temp/*.txt'):
    print(p)
# temp/[x].txt
# temp/1.txt
# temp/123.txt
スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事